Merge branch 'xc7' into xc7_gui

This commit is contained in:
Eddie Hung 2018-12-27 20:53:15 -08:00
commit ede0e93206
72 changed files with 6119 additions and 1583 deletions

12
.cirrus.yml Normal file
View File

@ -0,0 +1,12 @@
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
smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
test_ecp5_script: cd build && ./nextpnr-ecp5-test

View File

@ -0,0 +1,55 @@
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 autoconf cmake clang bison wget flex gperf \
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
libboost-all-dev qt5-default git libftdi-dev pkg-config
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/steveicarus/iverilog.git ;\
cd iverilog ;\
git reset --hard 172d7eb0a3665f89b91d601b5912c33acedc81e5 ;\
sh autoconf.sh ;\
./configure ;\
make -j $(nproc) ;\
make install ;\
rm -rf /usr/local/src/iverilog
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/YosysHQ/yosys.git ;\
cd yosys ;\
git reset --hard 47a5dfdaa4bd7d400c6e3d58476de80904df460d ;\
make -j $(nproc) ;\
make install ;\
rm -rf /usr/local/src/yosys
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

View File

@ -153,3 +153,25 @@ void pyinterpreter_release()
{ {
PyEval_ReleaseThread(m_threadState); PyEval_ReleaseThread(m_threadState);
} }
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode)
{
PyEval_AcquireThread(m_threadState);
*errorCode = 0;
std::string res;
FILE *fp = fopen(python_file, "r");
if (fp == NULL) {
*errorCode = 1;
res = "Fatal error: file not found " + std::string(python_file) + "\n";
return res;
}
if (PyRun_SimpleFile(fp, python_file)==-1) {
*errorCode = 1;
PyErr_Print();
}
res = redirector_take_output(m_threadState);
PyEval_ReleaseThread(m_threadState);
return res;
}

View File

@ -33,4 +33,5 @@ void pyinterpreter_initialize();
void pyinterpreter_finalize(); void pyinterpreter_finalize();
void pyinterpreter_aquire(); void pyinterpreter_aquire();
void pyinterpreter_release(); void pyinterpreter_release();
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode);
#endif // PYINTERPRETER_H #endif // PYINTERPRETER_H

View File

@ -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_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") set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe")
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g") set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
endif() endif()
set(CMAKE_DEFIN) set(CMAKE_DEFIN)

View File

@ -49,8 +49,9 @@ Getting started
### nextpnr-ice40 ### nextpnr-ice40
To build the iCE40 version of nextpnr, install [icestorm](http://www.clifford.at/icestorm/) with chipdbs installed in `/usr/local/share/icebox` To build the iCE40 version of nextpnr, install [icestorm](http://www.clifford.at/icestorm/) with chipdbs installed in `/usr/local/share/icebox`,
(or another location, which should be passed as -DICEBOX_ROOT=/path/to/icebox to CMake). or another location, which should be passed as `-DICEBOX_ROOT=/path/to/share/icebox` (ensure to point it to `share/icebox` and not where the
icebox binaries are installed) to CMake.
Then build and install `nextpnr-ice40` using the following commands: Then build and install `nextpnr-ice40` using the following commands:
``` ```

View File

@ -51,6 +51,7 @@ std::vector<CellChain> find_chains(const Context *ctx, F1 cell_type_predicate, F
CellChain chain; CellChain chain;
CellInfo *end = start; CellInfo *end = start;
while (end != nullptr) { while (end != nullptr) {
if (chained.insert(end->name).second)
chain.cells.push_back(end); chain.cells.push_back(end);
end = get_next(ctx, end); end = get_next(ctx, end);
} }

View File

@ -131,9 +131,9 @@ void CommandHandler::setupContext(Context *ctx)
} }
if (vm.count("quiet")) { 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 { } 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")) { if (vm.count("log")) {
@ -141,7 +141,7 @@ void CommandHandler::setupContext(Context *ctx)
logfile = std::ofstream(logfilename); logfile = std::ofstream(logfilename);
if (!logfile) if (!logfile)
log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); 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")) { if (vm.count("force")) {
@ -285,8 +285,8 @@ void CommandHandler::conflicting_options(const boost::program_options::variables
void CommandHandler::printFooter() void CommandHandler::printFooter()
{ {
int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING, 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, 0); error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0);
if (warning_count > 0 || error_count > 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, log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count,
error_count == 1 ? "" : "s"); error_count == 1 ? "" : "s");

View File

@ -84,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap)
return string; 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 // Trim newlines from the beginning
@ -132,7 +132,7 @@ void log_always(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv(format, ap, LogLevel::ALWAYS); logv(format, ap, LogLevel::ALWAYS_MSG);
va_end(ap); va_end(ap);
} }
@ -140,7 +140,7 @@ void log(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv(format, ap, LogLevel::LOG); logv(format, ap, LogLevel::LOG_MSG);
va_end(ap); va_end(ap);
} }
@ -148,7 +148,7 @@ void log_info(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv_prefixed("Info: ", format, ap, LogLevel::INFO); logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG);
va_end(ap); va_end(ap);
} }
@ -156,7 +156,7 @@ void log_warning(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING); logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG);
va_end(ap); va_end(ap);
} }
@ -164,7 +164,7 @@ void log_error(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
if (log_error_atexit) if (log_error_atexit)
log_error_atexit(); log_error_atexit();
@ -184,7 +184,7 @@ void log_nonfatal_error(const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
va_end(ap); va_end(ap);
had_nonfatal_error = true; had_nonfatal_error = true;
} }

View File

@ -44,11 +44,11 @@ struct log_execution_error_exception
enum class LogLevel enum class LogLevel
{ {
LOG, LOG_MSG,
INFO, INFO_MSG,
WARNING, WARNING_MSG,
ERROR, ERROR_MSG,
ALWAYS ALWAYS_MSG
}; };
extern std::vector<std::pair<std::ostream *, LogLevel>> log_streams; extern std::vector<std::pair<std::ostream *, LogLevel>> log_streams;

View File

@ -37,6 +37,8 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
if (driver_gb) if (driver_gb)
return 0; return 0;
int clock_count; int clock_count;
if (ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) == TMG_IGNORE)
return 0;
bool timing_driven = ctx->timing_driven && type == MetricType::COST && bool timing_driven = ctx->timing_driven && type == MetricType::COST &&
ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE; ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
delay_t negative_slack = 0; delay_t negative_slack = 0;

View File

@ -512,6 +512,21 @@ struct Router1
WireId next_wire = ctx->getPipDstWire(pip); WireId next_wire = ctx->getPipDstWire(pip);
next_delay += ctx->getWireDelay(next_wire).maxDelay(); next_delay += ctx->getWireDelay(next_wire).maxDelay();
#ifdef ARCH_XC7
// For BUFG routing, do not exit the global network until the destination tile is reached
if (ctx->isGlobalNet(net_info)) {
if (torc_info->wire_is_global[src_wire.index] && !torc_info->wire_is_global[next_wire.index]) {
const auto &arc = torc_info->pip_to_arc[pip.index];
const auto &next_tw = arc.getSinkTilewire();
const auto &next_loc = torc_info->tile_to_xy[next_tw.getTileIndex()];
const auto &dst_tw = torc_info->wire_to_tilewire[dst_wire.index];
const auto &dst_loc = torc_info->tile_to_xy[dst_tw.getTileIndex()];
if (next_loc.second != dst_loc.second || next_loc.first != dst_loc.first)
continue;
}
}
#endif
WireId conflictWireWire = WireId(), conflictPipWire = WireId(); WireId conflictWireWire = WireId(), conflictPipWire = WireId();
NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr;
@ -618,7 +633,11 @@ struct Router1
next_qw.penalty = next_penalty; next_qw.penalty = next_penalty;
next_qw.bonus = next_bonus; next_qw.bonus = next_bonus;
if (cfg.useEstimate) { if (cfg.useEstimate) {
#ifdef ARCH_XC7
next_qw.togo = ctx->estimateDelay(next_wire, imux_wire);
#else
next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); next_qw.togo = ctx->estimateDelay(next_wire, dst_wire);
#endif
delay_t this_est = next_qw.delay + next_qw.togo; delay_t this_est = next_qw.delay + next_qw.togo;
if (this_est / 2 - cfg.estimatePrecision > best_est) if (this_est / 2 - cfg.estimatePrecision > best_est)
continue; continue;

View File

@ -9,7 +9,7 @@ bool check_all_nets_driven(Context *ctx)
{ {
const bool debug = false; 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) { for (auto &cell_entry : ctx->cells) {
CellInfo *cell = cell_entry.second.get(); CellInfo *cell = cell_entry.second.get();

View File

@ -86,6 +86,7 @@ struct CriticalPath
}; };
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap; typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
struct Timing struct Timing
{ {
@ -95,6 +96,7 @@ struct Timing
delay_t min_slack; delay_t min_slack;
CriticalPathMap *crit_path; CriticalPathMap *crit_path;
DelayFrequency *slack_histogram; DelayFrequency *slack_histogram;
NetCriticalityMap *net_crit;
IdString async_clock; IdString async_clock;
struct TimingData struct TimingData
@ -105,13 +107,15 @@ struct Timing
unsigned max_path_length = 0; unsigned max_path_length = 0;
delay_t min_remaining_budget; delay_t min_remaining_budget;
bool false_startpoint = false; bool false_startpoint = false;
std::vector<delay_t> min_required;
std::unordered_map<ClockEvent, delay_t> arrival_time; std::unordered_map<ClockEvent, delay_t> arrival_time;
}; };
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr, 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), : 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$"))
{ {
} }
@ -410,7 +414,6 @@ struct Timing
while (crit_net) { while (crit_net) {
const PortInfo *crit_ipin = nullptr; const PortInfo *crit_ipin = nullptr;
delay_t max_arrival = std::numeric_limits<delay_t>::min(); delay_t max_arrival = std::numeric_limits<delay_t>::min();
// Look at all input ports on its driving cell // Look at all input ports on its driving cell
for (const auto &port : crit_net->driver.cell->ports) { for (const auto &port : crit_net->driver.cell->ports) {
if (port.second.type != PORT_IN || !port.second.net) if (port.second.type != PORT_IN || !port.second.net)
@ -424,14 +427,21 @@ struct Timing
int port_clocks; int port_clocks;
TimingPortClass portClass = TimingPortClass portClass =
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || if (portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) portClass == TMG_REGISTER_INPUT)
continue; continue;
// And find the fanin net with the latest arrival time // And find the fanin net with the latest arrival time
if (net_data.count(port.second.net) && if (net_data.count(port.second.net) &&
net_data.at(port.second.net).count(crit_pair.first.start)) { net_data.at(port.second.net).count(crit_pair.first.start)) {
const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival; auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
if (net_delays) {
for (auto &user : port.second.net->users)
if (user.port == port.first && user.cell == crit_net->driver.cell) {
net_arrival += ctx->getNetinfoRouteDelay(port.second.net, user);
break;
}
}
net_arrival += comb_delay.maxDelay();
if (net_arrival > max_arrival) { if (net_arrival > max_arrival) {
max_arrival = net_arrival; max_arrival = net_arrival;
crit_ipin = &port.second; crit_ipin = &port.second;
@ -441,7 +451,6 @@ struct Timing
if (!crit_ipin) if (!crit_ipin)
break; break;
// Now convert PortInfo* into a PortRef* // Now convert PortInfo* into a PortRef*
for (auto &usr : crit_ipin->net->users) { for (auto &usr : crit_ipin->net->users) {
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
@ -454,6 +463,180 @@ struct Timing
std::reverse(cp_ports.begin(), cp_ports.end()); 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; return min_slack;
} }
@ -601,6 +784,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
int port_clocks; int port_clocks;
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
IdString last_port = front_driver.port; IdString last_port = front_driver.port;
int clock_start = -1;
if (portClass == TMG_REGISTER_OUTPUT) { if (portClass == TMG_REGISTER_OUTPUT) {
for (int i = 0; i < port_clocks; i++) { for (int i = 0; i < port_clocks; i++) {
TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i); TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
@ -608,8 +792,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
if (clknet != nullptr && clknet->name == clocks.start.clock && if (clknet != nullptr && clknet->name == clocks.start.clock &&
clockInfo.edge == clocks.start.edge) { clockInfo.edge == clocks.start.edge) {
last_port = clockInfo.clock_port; last_port = clockInfo.clock_port;
total += clockInfo.clockToQ.maxDelay(); clock_start = i;
logic_total += clockInfo.clockToQ.maxDelay();
break; break;
} }
} }
@ -623,11 +806,15 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
auto &driver = net->driver; auto &driver = net->driver;
auto driver_cell = driver.cell; auto driver_cell = driver.cell;
DelayInfo comb_delay; DelayInfo comb_delay;
if (last_port == driver.port) { if (clock_start != -1) {
auto clockInfo = ctx->getPortClockingInfo(driver_cell, driver.port, clock_start);
comb_delay = clockInfo.clockToQ;
clock_start = -1;
} else if (last_port == driver.port) {
// Case where we start with a STARTPOINT etc // Case where we start with a STARTPOINT etc
comb_delay = ctx->getDelayFromNS(0); comb_delay = ctx->getDelayFromNS(0);
} else { } else {
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay);
} }
total += comb_delay.maxDelay(); total += comb_delay.maxDelay();
logic_total += comb_delay.maxDelay(); logic_total += comb_delay.maxDelay();
@ -766,4 +953,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 NEXTPNR_NAMESPACE_END

View File

@ -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, void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
bool warn_on_failure = 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 NEXTPNR_NAMESPACE_END
#endif #endif

624
common/timing_opt.cc Normal file
View File

@ -0,0 +1,624 @@
/*
* 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;
}
};
#ifndef ARCH_GENERIC
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;
}
};
#endif
} // 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
View 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

View File

@ -579,6 +579,8 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
return false; return false;
} else if (cell->type == id_DP16KD) { } else if (cell->type == id_DP16KD) {
return false; return false;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
return false;
} else { } else {
return false; return false;
} }
@ -669,8 +671,19 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
} }
return TMG_IGNORE; return TMG_IGNORE;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
if (port == id_CLK || port == id_ECLK) {
return TMG_CLOCK_INPUT;
} else if (port == id_IOLDO || port == id_IOLDOI || port == id_IOLDOD || port == id_IOLTO || port == id_PADDI ||
port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) {
return TMG_IGNORE;
} else { } else {
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
} else {
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
cell->name.c_str(this));
} }
} }
@ -743,6 +756,14 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
info.setup = getDelayFromNS(1); info.setup = getDelayFromNS(1);
info.hold = getDelayFromNS(0); info.hold = getDelayFromNS(0);
} }
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
info.clock_port = id_CLK;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ = getDelayFromNS(0.5);
} else {
info.setup = getDelayFromNS(0.1);
info.hold = getDelayFromNS(0);
}
} }
return info; return info;
} }

View File

@ -745,6 +745,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
} }
if (ci->attrs.count(ctx->id("SLEWRATE"))) if (ci->attrs.count(ctx->id("SLEWRATE")))
cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW")); cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW"));
std::string datamux_oddr = str_or_default(ci->params, ctx->id("DATAMUX_ODDR"), "PADDO");
if (datamux_oddr != "PADDO")
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_ODDR", datamux_oddr);
std::string datamux_mddr = str_or_default(ci->params, ctx->id("DATAMUX_MDDR"), "PADDO");
if (datamux_mddr != "PADDO")
cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_MDDR", datamux_mddr);
} else if (ci->type == ctx->id("DCCA")) { } else if (ci->type == ctx->id("DCCA")) {
// Nothing to do // Nothing to do
} else if (ci->type == ctx->id("DP16KD")) { } else if (ci->type == ctx->id("DP16KD")) {
@ -1078,6 +1084,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1)); int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));
cc.tilegroups.push_back(tg); cc.tilegroups.push_back(tg);
} else if (ci->type == id_IOLOGIC || ci->type == id_SIOLOGIC) {
Loc pio_loc = ctx->getBelLocation(ci->bel);
pio_loc.z -= ci->type == id_SIOLOGIC ? 2 : 4;
std::string pic_tile = get_pic_tile(ctx, ctx->getBelByLocation(pio_loc));
std::string prim = std::string("IOLOGIC") + "ABCD"[pio_loc.z];
for (auto &param : ci->params) {
if (param.first == ctx->id("DELAY.DEL_VALUE"))
cc.tiles[pic_tile].add_word(prim + "." + param.first.str(ctx),
int_to_bitvector(std::stoi(param.second), 7));
else
cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second);
}
} else if (ci->type == id_DCUA) { } else if (ci->type == id_DCUA) {
TileGroup tg; TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel); tg.tiles = get_dcu_tiles(ctx, ci->bel);

View File

@ -41,6 +41,22 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
new_cell->name = ctx->id(name); new_cell->name = ctx->id(name);
} }
new_cell->type = type; new_cell->type = type;
auto copy_bel_ports = [&]() {
// First find a Bel of the target type
BelId tgt;
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) == type) {
tgt = bel;
break;
}
}
NPNR_ASSERT(tgt != BelId());
for (auto port : ctx->getBelPins(tgt)) {
add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port));
}
};
if (type == ctx->id("TRELLIS_SLICE")) { if (type == ctx->id("TRELLIS_SLICE")) {
new_cell->params[ctx->id("MODE")] = "LOGIC"; new_cell->params[ctx->id("MODE")] = "LOGIC";
new_cell->params[ctx->id("GSR")] = "DISABLED"; new_cell->params[ctx->id("GSR")] = "DISABLED";
@ -111,11 +127,17 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
} else if (type == ctx->id("TRELLIS_IO")) { } else if (type == ctx->id("TRELLIS_IO")) {
new_cell->params[ctx->id("DIR")] = "INPUT"; new_cell->params[ctx->id("DIR")] = "INPUT";
new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33"; new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
new_cell->params[ctx->id("DATAMUX_ODDR")] = "PADDO";
new_cell->params[ctx->id("DATAMUX_MDDR")] = "PADDO";
add_port(ctx, new_cell.get(), "B", PORT_INOUT); add_port(ctx, new_cell.get(), "B", PORT_INOUT);
add_port(ctx, new_cell.get(), "I", PORT_IN); add_port(ctx, new_cell.get(), "I", PORT_IN);
add_port(ctx, new_cell.get(), "T", PORT_IN); add_port(ctx, new_cell.get(), "T", PORT_IN);
add_port(ctx, new_cell.get(), "O", PORT_OUT); add_port(ctx, new_cell.get(), "O", PORT_OUT);
add_port(ctx, new_cell.get(), "IOLDO", PORT_IN);
add_port(ctx, new_cell.get(), "IOLTO", PORT_IN);
} else if (type == ctx->id("LUT4")) { } else if (type == ctx->id("LUT4")) {
new_cell->params[ctx->id("INIT")] = "0"; new_cell->params[ctx->id("INIT")] = "0";
@ -150,6 +172,35 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str
add_port(ctx, new_cell.get(), "CLKI", PORT_IN); add_port(ctx, new_cell.get(), "CLKI", PORT_IN);
add_port(ctx, new_cell.get(), "CLKO", PORT_OUT); add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);
add_port(ctx, new_cell.get(), "CE", PORT_IN); add_port(ctx, new_cell.get(), "CE", PORT_IN);
} else if (type == id_IOLOGIC || type == id_SIOLOGIC) {
new_cell->params[ctx->id("MODE")] = "NONE";
new_cell->params[ctx->id("GSR")] = "DISABLED";
new_cell->params[ctx->id("CLKIMUX")] = "CLK";
new_cell->params[ctx->id("CLKOMUX")] = "CLK";
new_cell->params[ctx->id("LSRIMUX")] = "0";
new_cell->params[ctx->id("LSROMUX")] = "0";
new_cell->params[ctx->id("LSRMUX")] = "LSR";
new_cell->params[ctx->id("DELAY.OUTDEL")] = "DISABLED";
new_cell->params[ctx->id("DELAY.DEL_VALUE")] = "0";
new_cell->params[ctx->id("DELAY.WAIT_FOR_EDGE")] = "DISABLED";
if (type == id_IOLOGIC) {
new_cell->params[ctx->id("IDDRXN.MODE")] = "NONE";
new_cell->params[ctx->id("ODDRXN.MODE")] = "NONE";
new_cell->params[ctx->id("MIDDRX.MODE")] = "NONE";
new_cell->params[ctx->id("MODDRX.MODE")] = "NONE";
new_cell->params[ctx->id("MTDDRX.MODE")] = "NONE";
new_cell->params[ctx->id("IOLTOMUX")] = "NONE";
new_cell->params[ctx->id("MTDDRX.DQSW_INVERT")] = "DISABLED";
new_cell->params[ctx->id("MTDDRX.REGSET")] = "RESET";
new_cell->params[ctx->id("MIDDRX_MODDRX.WRCLKMUX")] = "NONE";
}
// Just copy ports from the Bel
copy_bel_ports();
} else { } else {
log_error("unable to create ECP5 cell of type %s", type.c_str(ctx)); log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
} }
@ -365,7 +416,7 @@ void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::u
ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
ctx->id("Y")); ctx->id("Y"));
if (tbuf) { if (tbuf) {
replace_port(tbuf, ctx->id("I"), trio, ctx->id("I")); replace_port(tbuf, ctx->id("A"), trio, ctx->id("I"));
// Need to invert E to form T // Need to invert E to form T
std::unique_ptr<CellInfo> inv_lut = create_ecp5_cell(ctx, ctx->id("LUT4"), trio->name.str(ctx) + "$invert_T"); std::unique_ptr<CellInfo> inv_lut = create_ecp5_cell(ctx, ctx->id("LUT4"), trio->name.str(ctx) + "$invert_T");
replace_port(tbuf, ctx->id("E"), inv_lut.get(), ctx->id("A")); replace_port(tbuf, ctx->id("E"), inv_lut.get(), ctx->id("A"));

View File

@ -1142,3 +1142,43 @@ X(PAD)
X(PADDI) X(PADDI)
X(PADDO) X(PADDO)
X(PADDT) X(PADDT)
X(IOLOGIC)
X(SIOLOGIC)
X(DI)
X(IOLDO)
X(IOLDOD)
X(IOLDOI)
X(IOLTO)
X(INDD)
X(LOADN)
X(MOVE)
X(DIRECTION)
X(TSDATA0)
X(TXDATA0)
X(TXDATA1)
X(RXDATA0)
X(RXDATA1)
X(INFF)
X(CFLAG)
X(ECLK)
X(TSDATA1)
X(TXDATA2)
X(TXDATA3)
X(RXDATA2)
X(RXDATA3)
X(TXDATA4)
X(TXDATA5)
X(TXDATA6)
X(RXDATA4)
X(RXDATA5)
X(RXDATA6)
X(DQSR90)
X(DQSW270)
X(DQSW)
X(RDPNTR0)
X(RDPNTR1)
X(RDPNTR2)
X(WRPNTR0)
X(WRPNTR1)
X(WRPNTR2)

View File

@ -58,6 +58,8 @@ class Ecp5GlobalRouter
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK || if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK)) user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
return true; return true;
if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK)
return true;
return false; return false;
} }
@ -298,6 +300,8 @@ class Ecp5GlobalRouter
} else { } else {
// Check for dedicated routing // Check for dedicated routing
if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) { 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; return 0;
} }
// Driver is locked // Driver is locked
@ -308,7 +312,7 @@ class Ecp5GlobalRouter
} }
// Return true if a short (<5) route exists between two wires // 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::queue<WireId> visit;
std::unordered_map<WireId, PipId> backtrace; std::unordered_map<WireId, PipId> backtrace;
@ -316,7 +320,7 @@ class Ecp5GlobalRouter
WireId cursor; WireId cursor;
while (true) { 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), // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx),
// ctx->getWireName(dst).c_str(ctx)); // ctx->getWireName(dst).c_str(ctx));
return false; return false;

View File

@ -299,7 +299,16 @@ class Ecp5Packer
// iobuf // iobuf
log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx), log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
ci->type.c_str(ctx), ci->name.c_str(ctx)); ci->type.c_str(ctx), ci->name.c_str(ctx));
NetInfo *net = trio->ports.at(ctx->id("B")).net; NetInfo *net = trio->ports.at(ctx->id("B")).net;
if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) &&
net->users.size() > 1) ||
(ci->type == ctx->id("$nextpnr_obuf") &&
(net->users.size() > 2 || net->driver.cell != nullptr)) ||
(ci->type == ctx->id("$nextpnr_iobuf") && ci->ports.at(ctx->id("I")).net != nullptr &&
ci->ports.at(ctx->id("I")).net->driver.cell != nullptr))
log_error("Pin B of %s '%s' connected to more than a single top level IO.\n",
trio->type.c_str(ctx), trio->name.c_str(ctx));
if (net != nullptr) { if (net != nullptr) {
ctx->nets.erase(net->name); ctx->nets.erase(net->name);
trio->ports.at(ctx->id("B")).net = nullptr; trio->ports.at(ctx->id("B")).net = nullptr;
@ -1323,13 +1332,206 @@ 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);
}
}
}
// Check if two nets have identical constant drivers
bool equal_constant(NetInfo *a, NetInfo *b)
{
if (a->driver.cell == nullptr || b->driver.cell == nullptr)
return (a->driver.cell == nullptr && b->driver.cell == nullptr);
if (a->driver.cell->type != ctx->id("GND") && a->driver.cell->type != ctx->id("VCC"))
return false;
return a->driver.cell->type == b->driver.cell->type;
}
// Pack IOLOGIC
void pack_iologic()
{
std::unordered_map<IdString, CellInfo *> pio_iologic;
auto set_iologic_sclk = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
NetInfo *sclk = nullptr;
if (prim->ports.count(port))
sclk = prim->ports[port].net;
if (sclk == nullptr) {
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "0";
} else {
iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "CLK";
if (iol->ports[id_CLK].net != nullptr) {
if (iol->ports[id_CLK].net != sclk && !equal_constant(iol->ports[id_CLK].net, sclk))
log_error("IOLOGIC '%s' has conflicting clocks '%s' and '%s'\n", iol->name.c_str(ctx),
iol->ports[id_CLK].net->name.c_str(ctx), sclk->name.c_str(ctx));
} else {
connect_port(ctx, sclk, iol, id_CLK);
}
}
if (prim->ports.count(port))
disconnect_port(ctx, prim, port);
};
auto set_iologic_lsr = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) {
NetInfo *lsr = nullptr;
if (prim->ports.count(port))
lsr = prim->ports[port].net;
if (lsr == nullptr) {
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "0";
} else {
iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "LSRMUX";
if (iol->ports[id_LSR].net != nullptr && !equal_constant(iol->ports[id_LSR].net, lsr)) {
if (iol->ports[id_LSR].net != lsr)
log_error("IOLOGIC '%s' has conflicting LSR signals '%s' and '%s'\n", iol->name.c_str(ctx),
iol->ports[id_LSR].net->name.c_str(ctx), lsr->name.c_str(ctx));
} else {
connect_port(ctx, lsr, iol, id_LSR);
}
}
if (prim->ports.count(port))
disconnect_port(ctx, prim, port);
};
auto set_iologic_mode = [&](CellInfo *iol, std::string mode) {
auto &curr_mode = iol->params[ctx->id("MODE")];
if (curr_mode != "NONE" && curr_mode != mode)
log_error("IOLOGIC '%s' has conflicting modes '%s' and '%s'\n", iol->name.c_str(ctx), curr_mode.c_str(),
mode.c_str());
curr_mode = mode;
};
auto create_pio_iologic = [&](CellInfo *pio, CellInfo *curr) {
if (!pio->attrs.count(ctx->id("BEL")))
log_error("IOLOGIC functionality (DDR, DELAY, DQS, etc) can only be used with pin-constrained PIO "
"(while processing '%s').\n",
curr->name.c_str(ctx));
BelId bel = ctx->getBelByName(ctx->id(pio->attrs.at(ctx->id("BEL"))));
NPNR_ASSERT(bel != BelId());
log_info("IOLOGIC component %s connected to PIO Bel %s\n", curr->name.c_str(ctx),
ctx->getBelName(bel).c_str(ctx));
Loc loc = ctx->getBelLocation(bel);
bool s = false;
if (loc.y == 0 || loc.y == (ctx->chip_info->height - 1))
s = true;
std::unique_ptr<CellInfo> iol =
create_ecp5_cell(ctx, s ? id_SIOLOGIC : id_IOLOGIC, pio->name.str(ctx) + "$IOL");
loc.z += s ? 2 : 4;
iol->attrs[ctx->id("BEL")] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx);
CellInfo *iol_ptr = iol.get();
pio_iologic[pio->name] = iol_ptr;
new_cells.push_back(std::move(iol));
return iol_ptr;
};
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == ctx->id("IDDRX1F")) {
CellInfo *pio = net_driven_by(ctx, ci->ports.at(ctx->id("D")).net, is_trellis_io, id_O);
if (pio == nullptr || ci->ports.at(ctx->id("D")).net->users.size() > 1)
log_error("IDDRX1F '%s' D input must be connected only to a top level input\n",
ci->name.c_str(ctx));
CellInfo *iol;
if (pio_iologic.count(pio->name))
iol = pio_iologic.at(pio->name);
else
iol = create_pio_iologic(pio, ci);
set_iologic_mode(iol, "IDDRX1_ODDRX1");
replace_port(ci, ctx->id("D"), iol, id_PADDI);
set_iologic_sclk(iol, ci, ctx->id("SCLK"), true);
set_iologic_lsr(iol, ci, ctx->id("RST"), true);
replace_port(ci, ctx->id("Q0"), iol, id_RXDATA0);
replace_port(ci, ctx->id("Q1"), iol, id_RXDATA1);
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
packed_cells.insert(cell.first);
} else if (ci->type == ctx->id("ODDRX1F")) {
CellInfo *pio = net_only_drives(ctx, ci->ports.at(ctx->id("Q")).net, is_trellis_io, id_I, true);
if (pio == nullptr)
log_error("ODDRX1F '%s' Q output must be connected only to a top level output\n",
ci->name.c_str(ctx));
CellInfo *iol;
if (pio_iologic.count(pio->name))
iol = pio_iologic.at(pio->name);
else
iol = create_pio_iologic(pio, ci);
set_iologic_mode(iol, "IDDRX1_ODDRX1");
replace_port(ci, ctx->id("Q"), iol, id_IOLDO);
if (!pio->ports.count(id_IOLDO)) {
pio->ports[id_IOLDO].name = id_IOLDO;
pio->ports[id_IOLDO].type = PORT_IN;
}
replace_port(pio, id_I, pio, id_IOLDO);
pio->params[ctx->id("DATAMUX_ODDR")] = "IOLDO";
set_iologic_sclk(iol, ci, ctx->id("SCLK"), false);
set_iologic_lsr(iol, ci, ctx->id("RST"), false);
replace_port(ci, ctx->id("D0"), iol, id_TXDATA0);
replace_port(ci, ctx->id("D1"), iol, id_TXDATA1);
iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED");
packed_cells.insert(cell.first);
}
}
flush_cells();
};
public: public:
void pack() void pack()
{ {
pack_io(); pack_io();
pack_iologic();
pack_ebr(); pack_ebr();
pack_dsps(); pack_dsps();
pack_dcus(); pack_dcus();
preplace_plls();
pack_constants(); pack_constants();
pack_dram(); pack_dram();
pack_carries(); pack_carries();

View File

@ -41,6 +41,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{ {
QSurfaceFormat fmt; QSurfaceFormat fmt;
fmt.setSamples(10); fmt.setSamples(10);
fmt.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(fmt); QSurfaceFormat::setDefaultFormat(fmt);
#ifdef _WIN32 #ifdef _WIN32
SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE); SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE);

View File

@ -22,5 +22,6 @@
<file>resources/route.png</file> <file>resources/route.png</file>
<file>resources/time_add.png</file> <file>resources/time_add.png</file>
<file>resources/open_json.png</file> <file>resources/open_json.png</file>
<file>resources/py.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -189,6 +189,12 @@ void BaseMainWindow::createMenusAndBars()
actionRoute->setEnabled(false); actionRoute->setEnabled(false);
connect(actionRoute, &QAction::triggered, task, &TaskManager::route); connect(actionRoute, &QAction::triggered, task, &TaskManager::route);
actionExecutePy = new QAction("Execute Python", this);
actionExecutePy->setIcon(QIcon(":/icons/resources/py.png"));
actionExecutePy->setStatusTip("Execute Python script");
actionExecutePy->setEnabled(true);
connect(actionExecutePy, &QAction::triggered, this, &BaseMainWindow::execute_python);
// Worker control toolbar actions // Worker control toolbar actions
actionPlay = new QAction("Play", this); actionPlay = new QAction("Play", this);
actionPlay->setIcon(QIcon(":/icons/resources/control_play.png")); actionPlay->setIcon(QIcon(":/icons/resources/control_play.png"));
@ -249,6 +255,8 @@ void BaseMainWindow::createMenusAndBars()
menuDesign->addAction(actionAssignBudget); menuDesign->addAction(actionAssignBudget);
menuDesign->addAction(actionPlace); menuDesign->addAction(actionPlace);
menuDesign->addAction(actionRoute); menuDesign->addAction(actionRoute);
menuDesign->addSeparator();
menuDesign->addAction(actionExecutePy);
// Add Help menu actions // Add Help menu actions
menuHelp->addAction(actionAbout); menuHelp->addAction(actionAbout);
@ -268,6 +276,7 @@ void BaseMainWindow::createMenusAndBars()
mainActionBar->addAction(actionAssignBudget); mainActionBar->addAction(actionAssignBudget);
mainActionBar->addAction(actionPlace); mainActionBar->addAction(actionPlace);
mainActionBar->addAction(actionRoute); mainActionBar->addAction(actionRoute);
mainActionBar->addAction(actionExecutePy);
// Add worker control toolbar // Add worker control toolbar
QToolBar *workerControlToolBar = new QToolBar("Worker"); QToolBar *workerControlToolBar = new QToolBar("Worker");
@ -412,6 +421,7 @@ void BaseMainWindow::disableActions()
actionAssignBudget->setEnabled(false); actionAssignBudget->setEnabled(false);
actionPlace->setEnabled(false); actionPlace->setEnabled(false);
actionRoute->setEnabled(false); actionRoute->setEnabled(false);
actionExecutePy->setEnabled(true);
actionPlay->setEnabled(false); actionPlay->setEnabled(false);
actionPause->setEnabled(false); actionPause->setEnabled(false);
@ -454,6 +464,14 @@ void BaseMainWindow::open_proj()
} }
} }
void BaseMainWindow::execute_python()
{
QString fileName = QFileDialog::getOpenFileName(this, QString("Execute Python"), QString(), QString("*.py"));
if (!fileName.isEmpty()) {
console->execute_python(fileName.toStdString());
}
}
void BaseMainWindow::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); } void BaseMainWindow::notifyChangeContext() { Q_EMIT contextChanged(ctx.get()); }
void BaseMainWindow::save_proj() void BaseMainWindow::save_proj()
{ {

View File

@ -78,6 +78,8 @@ class BaseMainWindow : public QMainWindow
void budget(); void budget();
void place(); void place();
void execute_python();
void pack_finished(bool status); void pack_finished(bool status);
void budget_finish(bool status); void budget_finish(bool status);
void place_finished(bool status); void place_finished(bool status);
@ -122,6 +124,9 @@ class BaseMainWindow : public QMainWindow
QAction *actionAssignBudget; QAction *actionAssignBudget;
QAction *actionPlace; QAction *actionPlace;
QAction *actionRoute; QAction *actionRoute;
QAction *actionExecutePy;
QAction *actionPlay; QAction *actionPlay;
QAction *actionPause; QAction *actionPause;
QAction *actionStop; QAction *actionStop;

View File

@ -76,4 +76,21 @@ void PythonConsole::moveCursorToEnd()
setTextCursor(cursor); setTextCursor(cursor);
} }
void PythonConsole::execute_python(std::string filename)
{
int errorCode = 0;
std::string res;
res = pyinterpreter_execute_file(filename.c_str(), &errorCode);
if (res.size()) {
if (errorCode) {
setTextColor(ERROR_COLOR);
} else {
setTextColor(OUTPUT_COLOR);
}
append(res.c_str());
setTextColor(NORMAL_COLOR);
moveCursorToEnd();
}
}
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -43,6 +43,7 @@ class PythonConsole : public QTextEdit, public ParseListener
void displayString(QString text); void displayString(QString text);
void moveCursorToEnd(); void moveCursorToEnd();
virtual void parseEvent(const ParseMessage &message); virtual void parseEvent(const ParseMessage &message);
void execute_python(std::string filename);
protected: protected:
static const QColor NORMAL_COLOR; static const QColor NORMAL_COLOR;

View File

@ -114,4 +114,6 @@ void PythonTab::clearBuffer() { console->clear(); }
void PythonTab::info(std::string str) { console->displayString(str.c_str()); } void PythonTab::info(std::string str) { console->displayString(str.c_str()); }
void PythonTab::execute_python(std::string filename) { console->execute_python(filename); }
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -45,6 +45,7 @@ class PythonTab : public QWidget
void newContext(Context *ctx); void newContext(Context *ctx);
void info(std::string str); void info(std::string str);
void clearBuffer(); void clearBuffer();
void execute_python(std::string filename);
private: private:
PythonConsole *console; PythonConsole *console;

BIN
gui/resources/py.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false)
TaskManager::~TaskManager() TaskManager::~TaskManager()
{ {
log_write_function = nullptr;
if (workerThread.isRunning()) if (workerThread.isRunning())
terminate_thread(); terminate_thread();
workerThread.quit(); workerThread.quit();

View File

@ -26,6 +26,7 @@
#include "nextpnr.h" #include "nextpnr.h"
#include "placer1.h" #include "placer1.h"
#include "router1.h" #include "router1.h"
#include "timing_opt.h"
#include "util.h" #include "util.h"
NEXTPNR_NAMESPACE_BEGIN 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())); } 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) if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
return TMG_IGNORE; return TMG_IGNORE;
return TMG_ENDPOINT; 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 TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const

View File

@ -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 // Lattice's weird string style params, not sure if
// prefixes other than 0b should be supported, only 0b features in docs // 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"); 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); raw = raw.substr(2);
value.resize(raw.length()); value.resize(raw.length());
for (int i = 0; i < (int)raw.length(); i++) { 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 { } 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++) for (int i = 0; i < p.second; i++)
value.push_back((ival >> i) & 0x1); 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")); 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 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 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_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; bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0;
@ -495,27 +503,21 @@ 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), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val);
} }
set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); set_config(ti, config.at(y).at(x), "NegClk", neg_trigger);
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 (!lvds) {
auto ieren = get_ieren(bi, x, y, z); auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez; int iex, iey, iez;
std::tie(iex, iey, iez) = ieren; std::tie(iex, iey, iez) = ieren;
NPNR_ASSERT(iez != -1); 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;
}
}
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) { 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.IE_" + std::to_string(iez), !input_en);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
@ -524,6 +526,48 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
} }
if (ctx->args.type == ArchArgs::UP5K) {
std::string pullup_resistor = "100K";
if (cell.second->attrs.count(ctx->id("PULLUP_RESISTOR")))
pullup_resistor = cell.second->attrs.at(ctx->id("PULLUP_RESISTOR"));
NPNR_ASSERT(pullup_resistor == "100K" || pullup_resistor == "10K" || pullup_resistor == "6P8K" ||
pullup_resistor == "3P3K");
if (iez == 0) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39",
(!pullup) || (pullup_resistor != "100K"));
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_36", pullup && pullup_resistor == "3P3K");
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_37", pullup && pullup_resistor == "6P8K");
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_38", pullup && pullup_resistor == "10K");
} else if (iez == 1) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35",
(!pullup) || (pullup_resistor != "100K"));
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_32", pullup && pullup_resistor == "3P3K");
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_33", pullup && pullup_resistor == "6P8K");
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_34", pullup && pullup_resistor == "10K");
}
}
} 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 (ctx->args.type == ArchArgs::UP5K) {
if (iez == 0) { if (iez == 0) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
@ -531,29 +575,6 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); 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 (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);
} 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);
}
if (ctx->args.type == ArchArgs::UP5K) {
if (ciez == 0) {
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup);
} else if (iez == 1) {
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup);
}
} }
} }
} else if (cell.second->type == ctx->id("SB_GB")) { } else if (cell.second->type == ctx->id("SB_GB")) {
@ -591,7 +612,8 @@ void write_asc(const Context *ctx, std::ostream &out)
{"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; {"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.")); 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."); 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 // No config needed
} else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) {
const BelInfoPOD &beli = ci.bel_data[bel.index]; const BelInfoPOD &beli = ci.bel_data[bel.index];
@ -688,7 +710,7 @@ void write_asc(const Context *ctx, std::ostream &out)
int iex, iey, iez; int iex, iey, iez;
std::tie(iex, iey, iez) = ieren; std::tie(iex, iey, iez) = ieren;
if (iez != -1) { 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) { if (z == 1) {
BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0});
const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0);

View File

@ -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(), "RGB0", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB1", PORT_OUT); add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB2", 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 { } else {
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
} }

View File

@ -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_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) 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") || return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||

View File

@ -62,7 +62,7 @@ class ChainConstrainer
bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) || bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) ||
(int(chains.back().cells.size()) > max_length); (int(chains.back().cells.size()) > max_length);
if (split_chain) { 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(); tile.pop_back();
chains.back().cells.back() = passout; chains.back().cells.back() = passout;
start_of_chain = true; 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("I3"), false) !=
net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), 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))) { (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); chains.back().cells.push_back(passout);
tile.push_back(passout); tile.push_back(passout);
start_of_chain = true;
} }
} }
++curr_cell; ++curr_cell;
@ -87,30 +87,75 @@ class ChainConstrainer
} }
// Insert a logic cell to legalise a COUT->fabric connection // 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); NPNR_ASSERT(cout_port.net != nullptr);
std::unique_ptr<CellInfo> lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); 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("LUT_INIT")] = "65280"; // 0xff00: O = I3
lc->params[ctx->id("CARRY_ENABLE")] = "1"; 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()); std::unique_ptr<NetInfo> co_i3_net(new NetInfo());
co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3"); co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3");
co_i3_net->driver = cout_port.net->driver; co_i3_net->driver = cout_port.net->driver;
PortRef i3_r; PortRef i3_r;
i3_r.port = ctx->id("I3"); i3_r.port = id_I3;
i3_r.cell = lc.get(); i3_r.cell = lc.get();
co_i3_net->users.push_back(i3_r); co_i3_net->users.push_back(i3_r);
PortRef o_r; PortRef o_r;
o_r.port = ctx->id("O"); o_r.port = id_O;
o_r.cell = lc.get(); o_r.cell = lc.get();
cout_port.net->driver = o_r; 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(); cout_port.net = co_i3_net.get();
IdString co_i3_name = co_i3_net->name; IdString co_i3_name = co_i3_net->name;
NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end());
ctx->nets[co_i3_name] = std::move(co_i3_net); 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; IdString name = lc->name;
ctx->assignCellInfo(lc.get()); ctx->assignCellInfo(lc.get());
ctx->cells[lc->name] = std::move(lc); ctx->cells[lc->name] = std::move(lc);
@ -163,8 +208,8 @@ class ChainConstrainer
void process_carries() void process_carries()
{ {
std::vector<CellChain> carry_chains = std::vector<CellChain> carry_chains = find_chains(
find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
[](const Context *ctx, const [](const Context *ctx, const
CellInfo *cell) { CellInfo *cell) {
@ -172,18 +217,20 @@ class ChainConstrainer
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
if (carry_prev != nullptr) if (carry_prev != nullptr)
return carry_prev; return carry_prev;
/*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT"));
ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/ if (i3_prev != nullptr)
return i3_prev;
return (CellInfo *)nullptr; return (CellInfo *)nullptr;
}, },
[](const Context *ctx, const CellInfo *cell) { [](const Context *ctx, const CellInfo *cell) {
CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, CellInfo *carry_next =
ctx->id("CIN"), false); net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false);
if (carry_next != nullptr) if (carry_next != nullptr)
return carry_next; return carry_next;
/*CellInfo *i3_next = CellInfo *i3_next =
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false);
false); if (i3_next != nullptr) return i3_next;*/ if (i3_next != nullptr)
return i3_next;
return (CellInfo *)nullptr; return (CellInfo *)nullptr;
}); });
std::unordered_set<IdString> chained; std::unordered_set<IdString> chained;

View File

@ -65,7 +65,13 @@ po::options_description Ice40CommandHandler::getArchOptions()
specific.add_options()("pcf", po::value<std::string>(), "PCF constraints file to ingest"); 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()("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()("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"); specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
specific.add_options()("pcf-allow-unconstrained", "don't require PCF to constrain all IO");
return specific; return specific;
} }
void Ice40CommandHandler::validate() void Ice40CommandHandler::validate()
@ -83,6 +89,8 @@ void Ice40CommandHandler::customAfterLoad(Context *ctx)
std::ifstream pcf(filename); std::ifstream pcf(filename);
if (!apply_pcf(ctx, filename, pcf)) if (!apply_pcf(ctx, filename, pcf))
log_error("Loading PCF failed.\n"); log_error("Loading PCF failed.\n");
} else {
log_warning("No PCF file specified; IO pins will be placed automatically\n");
} }
} }
void Ice40CommandHandler::customBitstream(Context *ctx) void Ice40CommandHandler::customBitstream(Context *ctx)
@ -152,7 +160,18 @@ std::unique_ptr<Context> Ice40CommandHandler::createContext()
if (vm.count("package")) if (vm.count("package"))
chipArgs.package = vm["package"].as<std::string>(); 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";
if (vm.count("pcf-allow-unconstrained"))
ctx->settings[ctx->id("pcf_allow_unconstrained")] = "1";
return ctx;
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])

View File

@ -518,10 +518,10 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
!is_sb_pll40(ctx, port.cell); !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]" : "", log_info("promoting %s%s%s%s (fanout %d)\n", net->name.c_str(ctx), is_reset ? " [reset]" : "",
is_logic ? " [logic]" : ""); 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::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); std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
@ -565,7 +565,8 @@ static void promote_globals(Context *ctx)
{ {
log_info("Promoting globals..\n"); log_info("Promoting globals..\n");
const int logic_fanout_thresh = 15; 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; std::map<IdString, int> clock_count, reset_count, cen_count, logic_count;
for (auto net : sorted(ctx->nets)) { for (auto net : sorted(ctx->nets)) {
NetInfo *ni = net.second; NetInfo *ni = net.second;
@ -637,18 +638,20 @@ static void promote_globals(Context *ctx)
}); });
if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh && 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_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(); 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_globals;
++prom_logics; ++prom_logics;
clock_count.erase(logicnet->name); clock_count.erase(logicnet->name);
reset_count.erase(logicnet->name); reset_count.erase(logicnet->name);
cen_count.erase(logicnet->name); cen_count.erase(logicnet->name);
logic_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(); 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_globals;
++prom_resets; ++prom_resets;
clock_count.erase(rstnet->name); clock_count.erase(rstnet->name);
@ -658,7 +661,7 @@ static void promote_globals(Context *ctx)
} else if (global_cen->second > global_clock->second && prom_cens < cens_available && } else if (global_cen->second > global_clock->second && prom_cens < cens_available &&
global_cen->second > enable_fanout_thresh) { global_cen->second > enable_fanout_thresh) {
NetInfo *cennet = ctx->nets[global_cen->first].get(); 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_globals;
++prom_cens; ++prom_cens;
clock_count.erase(cennet->name); clock_count.erase(cennet->name);
@ -667,7 +670,7 @@ static void promote_globals(Context *ctx)
logic_count.erase(cennet->name); logic_count.erase(cennet->name);
} else if (global_clock->second != 0) { } else if (global_clock->second != 0) {
NetInfo *clknet = ctx->nets[global_clock->first].get(); 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; ++prom_globals;
clock_count.erase(clknet->name); clock_count.erase(clknet->name);
reset_count.erase(clknet->name); reset_count.erase(clknet->name);
@ -679,6 +682,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 // spliceLUT adds a pass-through LUT LC between the given cell's output port
// and either all users or only non_LUT users. // and either all users or only non_LUT users.
static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs)
@ -844,6 +1043,10 @@ static void pack_special(Context *ctx)
ci->ports.erase(ctx->id("RGB0")); ci->ports.erase(ctx->id("RGB0"));
ci->ports.erase(ctx->id("RGB1")); ci->ports.erase(ctx->id("RGB1"));
ci->ports.erase(ctx->id("RGB2")); 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)) { } else if (is_sb_pll40(ctx, ci)) {
bool is_pad = is_sb_pll40_pad(ctx, ci); bool is_pad = is_sb_pll40_pad(ctx, ci);
bool is_core = !is_pad; bool is_core = !is_pad;
@ -877,13 +1080,17 @@ static void pack_special(Context *ctx)
} }
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
packed->params[ctx->id("FEEDBACK_PATH")] = std::string fbp_value = feedback_path == "DELAY"
feedback_path == "DELAY"
? "0" ? "0"
: feedback_path == "SIMPLE" ? "1" : feedback_path == "SIMPLE"
? "1"
: feedback_path == "PHASE_AND_DELAY" : feedback_path == "PHASE_AND_DELAY"
? "2" ? "2"
: feedback_path == "EXTERNAL" ? "6" : feedback_path; : 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)); packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
NetInfo *pad_packagepin_net = nullptr; NetInfo *pad_packagepin_net = nullptr;
@ -939,82 +1146,25 @@ static void pack_special(Context *ctx)
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); 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 // PLL must have been placed already in place_plls()
// information to then constrain the LOCK LUT. BelId pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
BelId pll_bel; NPNR_ASSERT(pll_bel != BelId());
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;
// A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared // Deal with PAD PLL peculiarities
// with PLLOUT_A.
if (is_pad) { if (is_pad) {
auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A);
NPNR_ASSERT(pad_packagepin_net != nullptr); NPNR_ASSERT(pad_packagepin_net != nullptr);
auto pll_packagepin_driver = pad_packagepin_net->driver; auto pll_packagepin_driver = pad_packagepin_net->driver;
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); 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));
}
auto packagepin_cell = pll_packagepin_driver.cell; auto packagepin_cell = pll_packagepin_driver.cell;
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); 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. // Set an attribute about this PLL's PAD SB_IO.
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; 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);
}
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), // Disconnect PACKAGEPIN (it's a physical HW link)
ctx->getBelName(bel).c_str(ctx)); for (auto user : pad_packagepin_net->users)
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) {
user.cell->ports.erase(user.port); user.cell->ports.erase(user.port);
} packagepin_cell->ports.erase(pll_packagepin_driver.port);
if (pad_packagepin_net->driver.cell != nullptr)
pad_packagepin_net->driver.cell->ports.erase(pad_packagepin_net->driver.port);
ctx->nets.erase(pad_packagepin_net->name); ctx->nets.erase(pad_packagepin_net->name);
pad_packagepin_net = nullptr; pad_packagepin_net = nullptr;
} }
@ -1028,20 +1178,26 @@ static void pack_special(Context *ctx)
log_info(" PLL '%s' has LOCK output, need to pass all outputs via LUT\n", ci->name.c_str(ctx)); log_info(" PLL '%s' has LOCK output, need to pass all outputs via LUT\n", ci->name.c_str(ctx));
bool found_lut = false; bool found_lut = false;
bool all_luts = true; bool all_luts = true;
bool found_carry = false;
unsigned int lut_count = 0; unsigned int lut_count = 0;
for (const auto &user : port.net->users) { for (const auto &user : port.net->users) {
NPNR_ASSERT(user.cell != nullptr); NPNR_ASSERT(user.cell != nullptr);
if (user.cell->type == ctx->id("ICESTORM_LC")) { if (user.cell->type == ctx->id("ICESTORM_LC")) {
if (bool_or_default(user.cell->params, ctx->id("CARRY_ENABLE"), false)) {
found_carry = true;
all_luts = false;
} else {
found_lut = true; found_lut = true;
lut_count++; lut_count++;
}
} else { } else {
all_luts = false; all_luts = false;
} }
} }
if (found_lut && all_luts) { if (found_lut && all_luts && lut_count < 8) {
// Every user is a LUT, carry on now. // Every user is a LUT, carry on now.
} else if (found_lut && !all_luts && lut_count < 8) { } else if (found_lut && !all_luts && !found_carry && lut_count < 8) {
// Strategy: create a pass-through LUT, move all non-LUT users behind it. // Strategy: create a pass-through LUT, move all non-LUT users behind it.
log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx)); log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx));
auto pt = spliceLUT(ctx, packed.get(), port.name, true); auto pt = spliceLUT(ctx, packed.get(), port.name, true);
@ -1118,7 +1274,9 @@ bool Arch::pack()
pack_nonlut_ffs(ctx); pack_nonlut_ffs(ctx);
pack_carries(ctx); pack_carries(ctx);
pack_ram(ctx); pack_ram(ctx);
place_plls(ctx);
pack_special(ctx); pack_special(ctx);
if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false))
promote_globals(ctx); promote_globals(ctx);
ctx->assignArchInfo(); ctx->assignArchInfo();
constrain_chains(ctx); constrain_chains(ctx);

View File

@ -21,6 +21,7 @@
#include "pcf.h" #include "pcf.h"
#include <sstream> #include <sstream>
#include "log.h" #include "log.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
@ -47,16 +48,42 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
if (words.size() == 0) if (words.size() == 0)
continue; continue;
std::string cmd = words.at(0); std::string cmd = words.at(0);
bool nowarn = false;
if (cmd == "set_io") { if (cmd == "set_io") {
size_t args_end = 1; size_t args_end = 1;
while (args_end < words.size() && words.at(args_end).at(0) == '-') std::vector<std::pair<IdString, std::string>> extra_attrs;
while (args_end < words.size() && words.at(args_end).at(0) == '-') {
const auto &setting = words.at(args_end);
if (setting == "-pullup") {
const auto &value = words.at(++args_end);
if (value == "yes" || value == "1")
extra_attrs.emplace_back(std::make_pair(ctx->id("PULLUP"), "1"));
else if (value == "no" || value == "0")
extra_attrs.emplace_back(std::make_pair(ctx->id("PULLUP"), "0"));
else
log_error("Invalid value '%s' for -pullup (on line %d)\n", value.c_str(), lineno);
} else if (setting == "-pullup_resistor") {
const auto &value = words.at(++args_end);
if (ctx->args.type != ArchArgs::UP5K)
log_error("Pullup resistance can only be set on UP5K (on line %d)\n", lineno);
if (value != "3P3K" && value != "6P8K" && value != "10K" && value != "100K")
log_error("Invalid value '%s' for -pullup_resistor (on line %d)\n", value.c_str(), lineno);
extra_attrs.emplace_back(std::make_pair(ctx->id("PULLUP_RESISTOR"), value));
} else if (setting == "-nowarn") {
nowarn = true;
} else if (setting == "--warn-no-port") {
} else {
log_warning("Ignoring PCF setting '%s' (on line %d)\n", setting.c_str(), lineno);
}
args_end++; args_end++;
}
if (args_end >= words.size() - 1) if (args_end >= words.size() - 1)
log_error("expected PCF syntax 'set_io cell pin' (on line %d)\n", lineno); log_error("expected PCF syntax 'set_io cell pin' (on line %d)\n", lineno);
std::string cell = words.at(args_end); std::string cell = words.at(args_end);
std::string pin = words.at(args_end + 1); std::string pin = words.at(args_end + 1);
auto fnd_cell = ctx->cells.find(ctx->id(cell)); auto fnd_cell = ctx->cells.find(ctx->id(cell));
if (fnd_cell == ctx->cells.end()) { if (fnd_cell == ctx->cells.end()) {
if (!nowarn)
log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno); log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno);
} else { } else {
BelId pin_bel = ctx->getPackagePinBel(pin); BelId pin_bel = ctx->getPackagePinBel(pin);
@ -67,11 +94,28 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
fnd_cell->second->attrs[ctx->id("BEL")] = ctx->getBelName(pin_bel).str(ctx); fnd_cell->second->attrs[ctx->id("BEL")] = ctx->getBelName(pin_bel).str(ctx);
log_info("constrained '%s' to bel '%s'\n", cell.c_str(), log_info("constrained '%s' to bel '%s'\n", cell.c_str(),
fnd_cell->second->attrs[ctx->id("BEL")].c_str()); fnd_cell->second->attrs[ctx->id("BEL")].c_str());
for (const auto &attr : extra_attrs)
fnd_cell->second->attrs[attr.first] = attr.second;
} }
} else { } else {
log_error("unsupported PCF command '%s' (on line %d)\n", cmd.c_str(), lineno); log_error("unsupported PCF command '%s' (on line %d)\n", cmd.c_str(), lineno);
} }
} }
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_obuf") ||
ci->type == ctx->id("$nextpnr_iobuf")) {
if (!ci->attrs.count(ctx->id("BEL"))) {
if (bool_or_default(ctx->settings, ctx->id("pcf_allow_unconstrained")))
log_warning("IO '%s' is unconstrained in PCF and will be automatically placed\n",
cell.first.c_str(ctx));
else
log_error("IO '%s' is unconstrained in PCF (override this error with "
"--pcf-allow-unconstrained)\n",
cell.first.c_str(ctx));
}
}
}
ctx->settings.emplace(ctx->id("input/pcf"), filename); ctx->settings.emplace(ctx->id("input/pcf"), filename);
return true; return true;
} catch (log_execution_error_exception) { } catch (log_execution_error_exception) {

View File

@ -22,7 +22,7 @@ for i in range(num_runs):
ascfile = "picorv32_work/picorv32_s{}.asc".format(run) ascfile = "picorv32_work/picorv32_s{}.asc".format(run)
if path.exists(ascfile): if path.exists(ascfile):
os.remove(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: if result.returncode != 0:
print("Run {} failed!".format(run)) print("Run {} failed!".format(run))
else: else:

6
ice40/smoketest/attosoc/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
attosoc_pnr.v
attosoc.asc
attosoc.json
attosoc_pnr_tb
testbench.vcd
output.txt

View File

@ -0,0 +1,10 @@
set_io clk E4
set_io led[0] B2
set_io led[1] F5
set_io led[2] B1
set_io led[3] C1
set_io led[4] C2
set_io led[5] F4
set_io led[6] D2
set_io led[7] G5

View File

@ -0,0 +1,127 @@
/*
* ECP5 PicoRV32 demo
*
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
* Copyright (C) 2018 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
`ifdef PICORV32_V
`error "attosoc.v must be read before picorv32.v!"
`endif
`define PICORV32_REGS picosoc_regs
module attosoc (
input clk,
output reg [7:0] led
);
reg [5:0] reset_cnt = 0;
wire resetn = &reset_cnt;
always @(posedge clk) begin
reset_cnt <= reset_cnt + !resetn;
end
parameter integer MEM_WORDS = 256;
parameter [31:0] STACKADDR = (4*MEM_WORDS); // end of memory
parameter [31:0] PROGADDR_RESET = 32'h 0000_0000; // ROM at 0x0
parameter integer ROM_BYTES = 256;
reg [7:0] rom [0:ROM_BYTES-1];
wire [31:0] rom_rdata = {rom[mem_addr+3], rom[mem_addr+2], rom[mem_addr+1], rom[mem_addr+0]};
initial $readmemh("firmware.hex", rom);
wire mem_valid;
wire mem_instr;
wire mem_ready;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
wire [31:0] mem_rdata;
wire rom_ready = mem_valid && mem_addr[31:24] == 8'h00;
wire iomem_valid;
wire iomem_ready;
wire [31:0] iomem_addr;
wire [31:0] iomem_wdata;
wire [3:0] iomem_wstrb;
wire [31:0] iomem_rdata;
assign iomem_valid = mem_valid && (mem_addr[31:24] > 8'h 01);
assign iomem_ready = 1'b1;
assign iomem_wstrb = mem_wstrb;
assign iomem_addr = mem_addr;
assign iomem_wdata = mem_wdata;
wire [31:0] spimemio_cfgreg_do;
always @(posedge clk)
if (iomem_valid && iomem_wstrb[0])
led <= iomem_wdata[7:0];
assign mem_ready = (iomem_valid && iomem_ready) || rom_ready;
assign mem_rdata = rom_rdata;
picorv32 #(
.STACKADDR(STACKADDR),
.PROGADDR_RESET(PROGADDR_RESET),
.PROGADDR_IRQ(32'h 0000_0000),
.BARREL_SHIFTER(0),
.COMPRESSED_ISA(0),
.ENABLE_MUL(0),
.ENABLE_DIV(0),
.ENABLE_IRQ(0),
.ENABLE_IRQ_QREGS(0)
) cpu (
.clk (clk ),
.resetn (resetn ),
.mem_valid (mem_valid ),
.mem_instr (mem_instr ),
.mem_ready (mem_ready ),
.mem_addr (mem_addr ),
.mem_wdata (mem_wdata ),
.mem_wstrb (mem_wstrb ),
.mem_rdata (mem_rdata )
);
endmodule
// Implementation note:
// Replace the following two modules with wrappers for your SRAM cells.
module picosoc_regs (
input clk, wen,
input [5:0] waddr,
input [5:0] raddr1,
input [5:0] raddr2,
input [31:0] wdata,
output [31:0] rdata1,
output [31:0] rdata2
);
reg [31:0] regs [0:31];
always @(posedge clk)
if (wen) regs[waddr[4:0]] <= wdata;
assign rdata1 = regs[raddr1[4:0]];
assign rdata2 = regs[raddr2[4:0]];
endmodule

View File

@ -0,0 +1,32 @@
module testbench();
integer out;
reg clk;
always #5 clk = (clk === 1'b0);
initial begin
out = $fopen("output.txt","w");
$dumpfile("testbench.vcd");
$dumpvars(0, testbench);
repeat (100) begin
repeat (256) @(posedge clk);
$display("+256 cycles");
end
$fclose(out);
#100;
$finish;
end
wire [7:0] led;
always @(led) begin
#1 $display("%b", led);
$fwrite(out, "%b\n", led);
end
attosoc uut (
.clk (clk ),
.led (led )
);
endmodule

View File

@ -0,0 +1,6 @@
@00000000
13 04 10 00 B7 04 00 02 93 09 00 10 13 04 14 00
63 44 34 01 13 04 20 00 13 09 20 00 63 5E 89 00
13 05 04 00 93 05 09 00 EF 00 80 01 63 08 05 00
13 09 19 00 6F F0 9F FE 23 A0 84 00 6F F0 1F FD
93 02 10 00 33 05 B5 40 E3 5E 55 FE 67 80 00 00

View File

@ -0,0 +1,16 @@
00000000
00000010
00000011
00000101
00000111
00001011
00001101
00010001
00010011
00010111
00011101
00011111
00100101
00101001
00101011
00101111

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -ex
yosys -q -p 'synth_ice40 -json attosoc.json -top attosoc' attosoc.v picorv32.v
$NEXTPNR --hx8k --json attosoc.json --pcf attosoc.pcf --asc attosoc.asc --freq 50
icetime -tmd hx8k -c 50 attosoc.asc
icebox_vlog -L -l -p attosoc.pcf -c -n attosoc attosoc.asc > attosoc_pnr.v
iverilog -o attosoc_pnr_tb attosoc_pnr.v attosoc_tb.v `yosys-config --datdir/ice40/cells_sim.v`
vvp attosoc_pnr_tb
diff output.txt golden.txt

View File

@ -594,7 +594,11 @@ void json_import_cell(Context *ctx, string modname, const std::vector<IdString>
if (type == PORT_IN || type == PORT_INOUT) { if (type == PORT_IN || type == PORT_INOUT) {
net->users.push_back(pr); net->users.push_back(pr);
} else if (type == PORT_OUT) { } 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; net->driver = pr;
} }
} }
@ -623,7 +627,9 @@ static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string
iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT}; iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT};
// Special case: input, etc, directly drives inout // Special case: input, etc, directly drives inout
if (net->driver.cell != nullptr) { if (net->driver.cell != nullptr) {
assert(net->driver.cell->type == ctx->id("$nextpnr_iobuf")); if (net->driver.cell->type != ctx->id("$nextpnr_iobuf"))
log_error("Top-level input '%s' also driven by %s.%s.\n", name.c_str(),
net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
net = net->driver.cell->ports.at(ctx->id("I")).net; net = net->driver.cell->ports.at(ctx->id("I")).net;
} }
assert(net->driver.cell == nullptr); assert(net->driver.cell == nullptr);
@ -686,8 +692,28 @@ void json_import(Context *ctx, string modname, JsonNode *node)
log_info("Importing module %s\n", modname.c_str()); log_info("Importing module %s\n", modname.c_str());
// Multiple labels might refer to the same net. For now we resolve conflicts thus:
// - names with fewer $ are always prefered
// - between equal $ counts, fewer .s are prefered
// - ties are resolved alphabetically
auto prefer_netlabel = [](const std::string &a, const std::string &b) {
if (b.empty())
return true;
long a_dollars = std::count(a.begin(), a.end(), '$'), b_dollars = std::count(b.begin(), b.end(), '$');
if (a_dollars < b_dollars)
return true;
else if (a_dollars > b_dollars)
return false;
long a_dots = std::count(a.begin(), a.end(), '.'), b_dots = std::count(b.begin(), b.end(), '.');
if (a_dots < b_dots)
return true;
else if (a_dots > b_dots)
return false;
return a < b;
};
// Import netnames // Import netnames
std::vector<IdString> netnames; std::vector<std::string> netlabels;
if (node->data_dict.count("netnames")) { if (node->data_dict.count("netnames")) {
JsonNode *cell_parent = node->data_dict.at("netnames"); JsonNode *cell_parent = node->data_dict.at("netnames");
for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) { for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) {
@ -701,15 +727,19 @@ void json_import(Context *ctx, string modname, JsonNode *node)
size_t num_bits = bits->data_array.size(); size_t num_bits = bits->data_array.size();
for (size_t i = 0; i < num_bits; i++) { for (size_t i = 0; i < num_bits; i++) {
int netid = bits->data_array.at(i)->data_number; int netid = bits->data_array.at(i)->data_number;
if (netid >= int(netnames.size())) if (netid >= int(netlabels.size()))
netnames.resize(netid + 1); netlabels.resize(netid + 1);
netnames.at(netid) = ctx->id( std::string name =
basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"))); basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"));
if (prefer_netlabel(name, netlabels.at(netid)))
netlabels.at(netid) = name;
} }
} }
} }
} }
std::vector<IdString> netids;
std::transform(netlabels.begin(), netlabels.end(), std::back_inserter(netids),
[ctx](const std::string &s) { return ctx->id(s); });
if (node->data_dict.count("cells")) { if (node->data_dict.count("cells")) {
JsonNode *cell_parent = node->data_dict.at("cells"); JsonNode *cell_parent = node->data_dict.at("cells");
// //
@ -719,7 +749,7 @@ void json_import(Context *ctx, string modname, JsonNode *node)
// //
for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) { for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) {
JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]); JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]);
json_import_cell(ctx, modname, netnames, here, cell_parent->data_dict_keys[cellid]); json_import_cell(ctx, modname, netids, here, cell_parent->data_dict_keys[cellid]);
} }
} }
@ -733,7 +763,7 @@ void json_import(Context *ctx, string modname, JsonNode *node)
JsonNode *here; JsonNode *here;
here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]); here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]);
json_import_toplevel_port(ctx, modname, netnames, ports_parent->data_dict_keys[portid], here); json_import_toplevel_port(ctx, modname, netids, ports_parent->data_dict_keys[portid], here);
} }
} }
check_all_nets_driven(ctx); check_all_nets_driven(ctx);

177
xc7/125MHz_to_60MHz.v Executable file
View File

@ -0,0 +1,177 @@
// file: clk_wiz_v3_6.v
//
// (c) Copyright 2008 - 2011 Xilinx, Inc. All rights reserved.
//
// This file contains confidential and proprietary information
// of Xilinx, Inc. and is protected under U.S. and
// international copyright and other intellectual property
// laws.
//
// DISCLAIMER
// This disclaimer is not a license and does not grant any
// rights to the materials distributed herewith. Except as
// otherwise provided in a valid license issued to you by
// Xilinx, and to the maximum extent permitted by applicable
// law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND
// WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES
// AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
// BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-
// INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and
// (2) Xilinx shall not be liable (whether in contract or tort,
// including negligence, or under any other theory of
// liability) for any loss or damage of any kind or nature
// related to, arising under or in connection with these
// materials, including for any direct, or any indirect,
// special, incidental, or consequential loss or damage
// (including loss of data, profits, goodwill, or any type of
// loss or damage suffered as a result of any action brought
// by a third party) even if such damage or loss was
// reasonably foreseeable or Xilinx had been advised of the
// possibility of the same.
//
// CRITICAL APPLICATIONS
// Xilinx products are not designed or intended to be fail-
// safe, or for use in any application requiring fail-safe
// performance, such as life-support or safety devices or
// systems, Class III medical devices, nuclear facilities,
// applications related to the deployment of airbags, or any
// other applications that could lead to death, personal
// injury, or severe property or environmental damage
// (individually and collectively, "Critical
// Applications"). Customer assumes the sole risk and
// liability of any use of Xilinx products in Critical
// Applications, subject only to applicable laws and
// regulations governing limitations on product liability.
//
// THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS
// PART OF THIS FILE AT ALL TIMES.
//
//----------------------------------------------------------------------------
// User entered comments
//----------------------------------------------------------------------------
// None
//
//----------------------------------------------------------------------------
// "Output Output Phase Duty Pk-to-Pk Phase"
// "Clock Freq (MHz) (degrees) Cycle (%) Jitter (ps) Error (ps)"
//----------------------------------------------------------------------------
// CLK_OUT1____60.000______0.000______50.0______231.736____234.038
//
//----------------------------------------------------------------------------
// "Input Clock Freq (MHz) Input Jitter (UI)"
//----------------------------------------------------------------------------
// __primary_________125.000____________0.010
`timescale 1ps/1ps
(* CORE_GENERATION_INFO = "clk_wiz_v3_6,clk_wiz_v3_6,{component_name=clk_wiz_v3_6,use_phase_alignment=false,use_min_o_jitter=false,use_max_i_jitter=false,use_dyn_phase_shift=false,use_inclk_switchover=false,use_dyn_reconfig=false,feedback_source=FDBK_AUTO,primtype_sel=MMCM_ADV,num_out_clk=1,clkin1_period=8.000,clkin2_period=10.000,use_power_down=false,use_reset=false,use_locked=false,use_inclk_stopped=false,use_status=false,use_freeze=false,use_clk_valid=false,feedback_type=SINGLE,clock_mgr_type=MANUAL,manual_override=false}" *)
module clk_wiz_v3_6
(// Clock in ports
input CLK_IN1,
// Clock out ports
output CLK_OUT1
);
// Input buffering
//------------------------------------
assign clkin1 = CLK_IN1;
// Clocking primitive
//------------------------------------
// Instantiation of the MMCM primitive
// * Unused inputs are tied off
// * Unused outputs are labeled unused
wire [15:0] do_unused;
wire drdy_unused;
wire psdone_unused;
wire locked_unused;
wire clkfbout;
wire clkfboutb_unused;
wire clkout0b_unused;
wire clkout1_unused;
wire clkout1b_unused;
wire clkout2_unused;
wire clkout2b_unused;
wire clkout3_unused;
wire clkout3b_unused;
wire clkout4_unused;
wire clkout5_unused;
wire clkout6_unused;
wire clkfbstopped_unused;
wire clkinstopped_unused;
MMCME2_ADV
#(.BANDWIDTH ("OPTIMIZED"),
.CLKOUT4_CASCADE ("FALSE"),
.COMPENSATION ("ZHOLD"),
.STARTUP_WAIT ("FALSE"),
.DIVCLK_DIVIDE (5),
.CLKFBOUT_MULT_F (40.500),
.CLKFBOUT_PHASE (0.000),
.CLKFBOUT_USE_FINE_PS ("FALSE"),
.CLKOUT0_DIVIDE_F (16.875),
.CLKOUT0_PHASE (0.000),
.CLKOUT0_DUTY_CYCLE (0.500),
.CLKOUT0_USE_FINE_PS ("FALSE"),
.CLKIN1_PERIOD (8.000),
.REF_JITTER1 (0.010))
mmcm_adv_inst
// Output clocks
(.CLKFBOUT (clkfbout),
.CLKFBOUTB (clkfboutb_unused),
.CLKOUT0 (clkout0),
.CLKOUT0B (clkout0b_unused),
.CLKOUT1 (clkout1_unused),
.CLKOUT1B (clkout1b_unused),
.CLKOUT2 (clkout2_unused),
.CLKOUT2B (clkout2b_unused),
.CLKOUT3 (clkout3_unused),
.CLKOUT3B (clkout3b_unused),
.CLKOUT4 (clkout4_unused),
.CLKOUT5 (clkout5_unused),
.CLKOUT6 (clkout6_unused),
// Input clock control
.CLKFBIN (clkfbout),
.CLKIN1 (clkin1),
.CLKIN2 (1'b0),
// Tied to always select the primary input clock
.CLKINSEL (1'b1),
// Ports for dynamic reconfiguration
.DADDR (7'h0),
.DCLK (1'b0),
.DEN (1'b0),
.DI (16'h0),
.DO (do_unused),
.DRDY (drdy_unused),
.DWE (1'b0),
// Ports for dynamic phase shift
.PSCLK (1'b0),
.PSEN (1'b0),
.PSINCDEC (1'b0),
.PSDONE (psdone_unused),
// Other control and status signals
.LOCKED (locked_unused),
.CLKINSTOPPED (clkinstopped_unused),
.CLKFBSTOPPED (clkfbstopped_unused),
.PWRDWN (1'b0),
.RST (1'b0));
// Output buffering
//-----------------------------------
//BUFG clkout1_buf
// (.O (CLK_OUT1),
// .I (clkout0));
// BUFG not currently supported
BUFGCTRL clkout1_buf (
.I0(clkout0),
.CE0(1'b1),
.S0(1'b1),
.O(CLK_OUT1)
);
endmodule

View File

@ -28,27 +28,8 @@
#include "router1.h" #include "router1.h"
#include "util.h" #include "util.h"
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/unordered_map.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
struct nextpnr_binary_iarchive : public boost::archive::binary_iarchive {
nextpnr_binary_iarchive(boost::iostreams::filtering_istreambuf &ifs, NEXTPNR_NAMESPACE::BaseCtx* ctx, const std::string& inDeviceName, const std::string& inPackageName) : boost::archive::binary_iarchive(ifs), ctx(ctx), inDeviceName(inDeviceName), inPackageName(inPackageName) {}
NEXTPNR_NAMESPACE::BaseCtx *ctx;
std::string inDeviceName, inPackageName;
};
struct nextpnr_binary_oarchive : public boost::archive::binary_oarchive {
nextpnr_binary_oarchive(boost::iostreams::filtering_ostreambuf &ofs, NEXTPNR_NAMESPACE::BaseCtx* ctx) : boost::archive::binary_oarchive(ofs), ctx(ctx) {}
NEXTPNR_NAMESPACE::BaseCtx *ctx;
};
#include "torc/common/DirectoryTree.hpp" #include "torc/common/DirectoryTree.hpp"
//#define TORC_INFO_DB "torc_info.ar"
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
std::unique_ptr<const TorcInfo> torc_info; std::unique_ptr<const TorcInfo> torc_info;
@ -57,6 +38,16 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
{ {
static const boost::regex re_loc(".+_X(\\d+)Y(\\d+)"); static const boost::regex re_loc(".+_X(\\d+)Y(\\d+)");
boost::cmatch what; boost::cmatch what;
tile_to_xy.resize(tiles.getTileCount());
for (TileIndex tileIndex(0); tileIndex < tiles.getTileCount(); tileIndex++) {
const auto &tileInfo = tiles.getTileInfo(tileIndex);
if (!boost::regex_match(tileInfo.getName(), what, re_loc))
throw;
const auto x = boost::lexical_cast<int>(what.str(1));
const auto y = boost::lexical_cast<int>(what.str(2));
tile_to_xy[tileIndex] = std::make_pair(x,y);
}
bel_to_site_index.reserve(sites.getSiteCount() * 4); bel_to_site_index.reserve(sites.getSiteCount() * 4);
bel_to_loc.reserve(sites.getSiteCount() * 4); bel_to_loc.reserve(sites.getSiteCount() * 4);
site_index_to_bel.resize(sites.getSiteCount()); site_index_to_bel.resize(sites.getSiteCount());
@ -67,11 +58,8 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const auto &site = sites.getSite(i); const auto &site = sites.getSite(i);
const auto &pd = site.getPrimitiveDefPtr(); const auto &pd = site.getPrimitiveDefPtr();
const auto &type = pd->getName(); const auto &type = pd->getName();
const auto &tileInfo = tiles.getTileInfo(site.getTileIndex()); int x, y;
if (!boost::regex_match(tileInfo.getName(), what, re_loc)) std::tie(x,y) = tile_to_xy[site.getTileIndex()];
throw;
const auto x = boost::lexical_cast<int>(what.str(1));
const auto y = boost::lexical_cast<int>(what.str(2));
if (type == "SLICEL" || type == "SLICEM") { if (type == "SLICEL" || type == "SLICEM") {
bel_to_site_index.push_back(i); bel_to_site_index.push_back(i);
@ -83,7 +71,6 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
if (!boost::regex_match(site_name.c_str(), what, re_loc)) if (!boost::regex_match(site_name.c_str(), what, re_loc))
throw; throw;
const auto sx = boost::lexical_cast<int>(what.str(1)); const auto sx = boost::lexical_cast<int>(what.str(1));
const auto site_name_back = site_name.back();
if ((sx & 1) == 0) { if ((sx & 1) == 0) {
bel_to_loc.emplace_back(x, y, 0); bel_to_loc.emplace_back(x, y, 0);
bel_to_loc.emplace_back(x, y, 1); bel_to_loc.emplace_back(x, y, 1);
@ -100,6 +87,14 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
} else if (type == "IOB33S" || type == "IOB33M") { } else if (type == "IOB33S" || type == "IOB33M") {
bel_to_site_index.push_back(i); bel_to_site_index.push_back(i);
site_index_to_type[i] = id_IOB33; site_index_to_type[i] = id_IOB33;
// TODO: Fix z when two IOBs on same tile
bel_to_loc.emplace_back(x, y, 0);
site_index_to_bel[i] = b;
++b.index;
} else if (type == "IOB18S" || type == "IOB18M") {
bel_to_site_index.push_back(i);
site_index_to_type[i] = id_IOB18;
// TODO: Fix z when two IOBs on same tile
bel_to_loc.emplace_back(x, y, 0); bel_to_loc.emplace_back(x, y, 0);
site_index_to_bel[i] = b; site_index_to_bel[i] = b;
++b.index; ++b.index;
@ -122,11 +117,11 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const boost::regex re_BOUNCE_NS("(BYP|FAN)_BOUNCE_[NS]3_\\d"); const boost::regex re_BOUNCE_NS("(BYP|FAN)_BOUNCE_[NS]3_\\d");
const boost::regex re_FAN("FAN(_ALT)?\\d"); const boost::regex re_FAN("FAN(_ALT)?\\d");
const boost::regex re_CLB_I1_6("CLBL[LM]_(L|LL|M)_[A-D]([1-6])"); const boost::regex re_CLB_I1_6("CLBL[LM]_(L|LL|M)_[A-D]([1-6])");
const boost::regex bufg_i("(CMT|CLK)_BUFG_BUFGCTRL\\d+_I0"); const boost::regex bufg_i("CLK_BUFG_BUFGCTRL\\d+_I0");
const boost::regex bufg_o("(CMT|CLK)_BUFG_BUFGCTRL\\d+_O"); const boost::regex bufg_o("CLK_BUFG_BUFGCTRL\\d+_O");
const boost::regex int_clk("CLK(_L)?[01]"); const boost::regex hrow("CLK_HROW_CLK[01]_[34]");
const boost::regex gclk("GCLK_(L_)?B\\d+(_EAST|_WEST)?");
std::unordered_map</*TileTypeIndex*/ unsigned, std::vector<delay_t>> delay_lookup; std::unordered_map</*TileTypeIndex*/ unsigned, std::vector<delay_t>> delay_lookup;
std::unordered_map<Segments::SegmentReference, TileIndex> segment_to_anchor;
Tilewire currentTilewire; Tilewire currentTilewire;
WireId w; WireId w;
w.index = 0; w.index = 0;
@ -141,8 +136,28 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const auto &currentSegment = segments.getTilewireSegment(currentTilewire); const auto &currentSegment = segments.getTilewireSegment(currentTilewire);
if (!currentSegment.isTrivial()) { if (!currentSegment.isTrivial()) {
if (currentSegment.getAnchorTileIndex() != tileIndex) auto r = segment_to_anchor.emplace(currentSegment, currentSegment.getAnchorTileIndex());
if (r.second) {
TilewireVector segment;
const_cast<DDB &>(*ddb).expandSegment(currentTilewire, segment, DDB::eExpandDirectionNone);
// expand all of the arcs
TilewireVector::const_iterator sep = segment.begin();
TilewireVector::const_iterator see = segment.end();
while(sep < see) {
// expand the tilewire sinks
const Tilewire& tilewire = *sep++;
const auto &tileInfo = tiles.getTileInfo(tilewire.getTileIndex());
const auto &tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
if (boost::starts_with(tileTypeName, "INT") || boost::starts_with(tileTypeName, "CLB")) {
r.first->second = tilewire.getTileIndex();
break;
}
}
}
if (r.first->second != tileIndex)
continue; continue;
segment_to_wire.emplace(currentSegment, w); segment_to_wire.emplace(currentSegment, w);
} else } else
trivial_to_wire.emplace(currentTilewire, w); trivial_to_wire.emplace(currentTilewire, w);
@ -224,9 +239,11 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
++w.index; ++w.index;
} }
} }
segment_to_anchor.clear();
wire_to_tilewire.shrink_to_fit(); wire_to_tilewire.shrink_to_fit();
wire_to_delay.shrink_to_fit(); wire_to_delay.shrink_to_fit();
num_wires = wire_to_tilewire.size(); num_wires = wire_to_tilewire.size();
wire_is_global.resize(num_wires);
wire_to_pips_downhill.resize(num_wires); wire_to_pips_downhill.resize(num_wires);
// std::unordered_map<Arc, int> arc_to_pip; // std::unordered_map<Arc, int> arc_to_pip;
@ -238,24 +255,45 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const auto &currentTilewire = wire_to_tilewire[w.index]; const auto &currentTilewire = wire_to_tilewire[w.index];
if (currentTilewire.isUndefined()) if (currentTilewire.isUndefined())
continue; continue;
arcs.clear();
const auto &tileInfo = tiles.getTileInfo(currentTilewire.getTileIndex()); const auto &tileInfo = tiles.getTileInfo(currentTilewire.getTileIndex());
const auto tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex()); const auto tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
const bool clb = boost::starts_with( const bool clb = boost::starts_with(
tileTypeName, "CLB"); // Disable all CLB route-throughs (i.e. LUT in->out, LUT A->AMUX, for now) tileTypeName, "CLB"); // Disable all CLB route-throughs (i.e. LUT in->out, LUT A->AMUX, for now)
arcs.clear();
const_cast<DDB &>(*ddb).expandSegmentSinks(currentTilewire, arcs, DDB::eExpandDirectionNone,
false /* inUseTied */, true /*inUseRegular */,
true /* inUseIrregular */, !clb /* inUseRoutethrough */);
auto &pips = wire_to_pips_downhill[w.index]; auto &pips = wire_to_pips_downhill[w.index];
pips.reserve(arcs.size()); const bool clk_tile = boost::starts_with(tileTypeName, "CLK");
const bool clk_tile = boost::starts_with(tileTypeName, "CMT") || boost::starts_with(tileTypeName, "CLK");
const bool int_tile = boost::starts_with(tileTypeName, "INT"); bool global_tile = false;
arcs.clear();
//const_cast<DDB &>(*ddb).expandSegmentSinks(currentTilewire, arcs, DDB::eExpandDirectionNone,
// false /* inUseTied */, true /*inUseRegular */,
// true /* inUseIrregular */, !clb /* inUseRoutethrough */);
{
// expand the segment
TilewireVector segment;
const_cast<DDB &>(*ddb).expandSegment(currentTilewire, segment, DDB::eExpandDirectionNone);
// expand all of the arcs
TilewireVector::const_iterator sep = segment.begin();
TilewireVector::const_iterator see = segment.end();
while(sep < see) {
// expand the tilewire sinks
const Tilewire& tilewire = *sep++;
const auto &tileInfo = tiles.getTileInfo(tilewire.getTileIndex());
const auto &tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
global_tile = global_tile || boost::starts_with(tileTypeName, "CLK") || boost::starts_with(tileTypeName, "HCLK") || boost::starts_with(tileTypeName, "CFG");
TilewireVector sinks;
const_cast<DDB &>(*ddb).expandTilewireSinks(tilewire, sinks, false /*inUseTied*/, true /*inUseRegular*/, true /*inUseIrregular*/,
!clb /* inUseRoutethrough */);
// rewrite the sinks as arcs
TilewireVector::const_iterator sip = sinks.begin();
TilewireVector::const_iterator sie = sinks.end();
while(sip < sie) {
Arc a(tilewire, *sip++);
for (const auto &a : arcs) {
// Disable BUFG I0 -> O routethrough // Disable BUFG I0 -> O routethrough
if (clk_tile) { if (clk_tile) {
ewi.set(a.getSourceTilewire()); ewi.set(a.getSourceTilewire());
@ -265,33 +303,29 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
continue; continue;
} }
} }
// Disable CLK inputs from being driven from the fabric (must be from global clock network)
else if (int_tile) { // Disable entering HROW from INT_[LR].CLK[01]
ewi.set(a.getSinkTilewire()); if (boost::starts_with(tileTypeName, "CLK_HROW")) {
if (boost::regex_match(ewi.mWireName, int_clk)) {
ewi.set(a.getSourceTilewire()); ewi.set(a.getSourceTilewire());
if (!boost::regex_match(ewi.mWireName, gclk)) if (boost::regex_match(ewi.mWireName, hrow))
continue; continue;
} }
}
pips.emplace_back(p); pips.emplace_back(p);
pip_to_arc.emplace_back(a); pip_to_arc.emplace_back(a);
// arc_to_pip.emplace(a, p.index); // arc_to_pip.emplace(a, p.index);
const auto &tw = a.getSinkTilewire();
pip_to_dst_wire.emplace_back(tilewire_to_wire(tw));
++p.index; ++p.index;
} }
}
}
pips.shrink_to_fit(); pips.shrink_to_fit();
if (global_tile)
wire_is_global[w.index] = true;
} }
pip_to_arc.shrink_to_fit(); pip_to_arc.shrink_to_fit();
num_pips = pip_to_arc.size(); num_pips = pip_to_arc.size();
pip_to_dst_wire.reserve(num_pips);
for (const auto &arc : pip_to_arc) {
const auto &tw = arc.getSinkTilewire();
pip_to_dst_wire.emplace_back(tilewire_to_wire(tw));
}
height = (int)tiles.getRowCount(); height = (int)tiles.getRowCount();
width = (int)tiles.getColCount(); width = (int)tiles.getColCount();
} }
@ -316,29 +350,9 @@ Arch::Arch(ArchArgs args) : args(args)
{ {
torc::common::DirectoryTree directoryTree("/opt/torc/src/torc"); torc::common::DirectoryTree directoryTree("/opt/torc/src/torc");
if (args.type == ArchArgs::Z020) { if (args.type == ArchArgs::Z020) {
#ifdef TORC_INFO_DB
std::ifstream ifs(TORC_INFO_DB, std::ios::binary);
if (ifs) {
boost::iostreams::filtering_istreambuf fifs;
fifs.push(boost::iostreams::zlib_decompressor());
fifs.push(ifs);
nextpnr_binary_iarchive ia(fifs, this, "xc7z020", args.package);
ia >> torc_info;
} else
#endif
{
torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7z020", args.package)); torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7z020", args.package));
#ifdef TORC_INFO_DB } else if (args.type == ArchArgs::VX980) {
std::ofstream ofs(TORC_INFO_DB, std::ios::binary); torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7vx980t", args.package));
if (ofs) {
boost::iostreams::filtering_ostreambuf fofs;
fofs.push(boost::iostreams::zlib_compressor());
fofs.push(ofs);
nextpnr_binary_oarchive oa(fofs, this);
oa << torc_info;
}
#endif
}
} else { } else {
log_error("Unsupported XC7 chip type.\n"); log_error("Unsupported XC7 chip type.\n");
} }
@ -351,21 +365,9 @@ Arch::Arch(ArchArgs args) : args(args)
log_info("Number of pips: %d\n", torc_info->num_pips); log_info("Number of pips: %d\n", torc_info->num_pips);
} }
// package_info = nullptr;
// for (int i = 0; i < chip_info->num_packages; i++) {
// if (chip_info->packages_data[i].name.get() == args.package) {
// package_info = &(chip_info->packages_data[i]);
// break;
// }
// }
// if (package_info == nullptr)
// log_error("Unsupported package '%s'.\n", args.package.c_str());
// bel_carry.resize(chip_info->num_bels);
bel_to_cell.resize(torc_info->num_bels); bel_to_cell.resize(torc_info->num_bels);
wire_to_net.resize(torc_info->num_wires); wire_to_net.resize(torc_info->num_wires);
pip_to_net.resize(torc_info->num_pips); pip_to_net.resize(torc_info->num_pips);
// switches_locked.resize(chip_info->num_switches);
} }
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -374,6 +376,8 @@ std::string Arch::getChipName() const
{ {
if (args.type == ArchArgs::Z020) { if (args.type == ArchArgs::Z020) {
return "z020"; return "z020";
} else if (args.type == ArchArgs::VX980) {
return "vx980";
} else { } else {
log_error("Unsupported XC7 chip type.\n"); log_error("Unsupported XC7 chip type.\n");
} }
@ -385,6 +389,8 @@ IdString Arch::archArgsToId(ArchArgs args) const
{ {
if (args.type == ArchArgs::Z020) if (args.type == ArchArgs::Z020)
return id("z020"); return id("z020");
if (args.type == ArchArgs::VX980)
return id("vx980");
return IdString(); return IdString();
} }
@ -510,10 +516,9 @@ WireId Arch::getBelPinWire(BelId bel, IdString pin) const
throw; throw;
} }
} }
} else if (bel_type == id_PS7) { } else if (bel_type == id_PS7 || bel_type == id_MMCME2_ADV) {
// e.g. Convert DDRARB[0] -> DDRARB0 // e.g. Convert DDRARB[0] -> DDRARB0
boost::erase_all(pin_name, "["); pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());
boost::erase_all(pin_name, "]");
} }
auto site_index = torc_info->bel_to_site_index[bel.index]; auto site_index = torc_info->bel_to_site_index[bel.index];
@ -525,49 +530,12 @@ WireId Arch::getBelPinWire(BelId bel, IdString pin) const
pin_name.c_str()); pin_name.c_str());
return torc_info->tilewire_to_wire(tw); return torc_info->tilewire_to_wire(tw);
// NPNR_ASSERT(bel != BelId());
//
// int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;
// const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get();
//
// if (num_bel_wires < 7) {
// for (int i = 0; i < num_bel_wires; i++) {
// if (bel_wires[i].port == pin.index) {
// ret.index = bel_wires[i].wire_index;
// break;
// }
// }
// } else {
// int b = 0, e = num_bel_wires - 1;
// while (b <= e) {
// int i = (b + e) / 2;
// if (bel_wires[i].port == pin.index) {
// ret.index = bel_wires[i].wire_index;
// break;
// }
// if (bel_wires[i].port > pin.index)
// e = i - 1;
// else
// b = i + 1;
// }
// }
//
//return ret;
} }
std::vector<IdString> Arch::getBelPins(BelId bel) const std::vector<IdString> Arch::getBelPins(BelId bel) const
{ {
std::vector<IdString> ret; std::vector<IdString> ret;
NPNR_ASSERT("TODO");
/* NPNR_ASSERT(bel != BelId());
int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get();
for (int i = 0; i < num_bel_wires; i++)
ret.push_back(IdString(bel_wires[i].port));
*/
return ret; return ret;
} }
@ -576,7 +544,6 @@ std::vector<IdString> Arch::getBelPins(BelId bel) const
WireId Arch::getWireByName(IdString name) const WireId Arch::getWireByName(IdString name) const
{ {
WireId ret; WireId ret;
if (wire_by_name.empty()) { if (wire_by_name.empty()) {
for (int i = 0; i < torc_info->num_wires; i++) for (int i = 0; i < torc_info->num_wires; i++)
wire_by_name[id(torc_info->wire_to_name(i))] = i; wire_by_name[id(torc_info->wire_to_name(i))] = i;
@ -592,38 +559,7 @@ WireId Arch::getWireByName(IdString name) const
IdString Arch::getWireType(WireId wire) const IdString Arch::getWireType(WireId wire) const
{ {
NPNR_ASSERT(wire != WireId()); NPNR_ASSERT(wire != WireId());
// switch (chip_info->wire_data[wire.index].type) { NPNR_ASSERT("TODO");
// case WireInfoPOD::WIRE_TYPE_NONE:
// return IdString();
// case WireInfoPOD::WIRE_TYPE_GLB2LOCAL:
// return id("GLB2LOCAL");
// case WireInfoPOD::WIRE_TYPE_GLB_NETWK:
// return id("GLB_NETWK");
// case WireInfoPOD::WIRE_TYPE_LOCAL:
// return id("LOCAL");
// case WireInfoPOD::WIRE_TYPE_LUTFF_IN:
// return id("LUTFF_IN");
// case WireInfoPOD::WIRE_TYPE_LUTFF_IN_LUT:
// return id("LUTFF_IN_LUT");
// case WireInfoPOD::WIRE_TYPE_LUTFF_LOUT:
// return id("LUTFF_LOUT");
// case WireInfoPOD::WIRE_TYPE_LUTFF_OUT:
// return id("LUTFF_OUT");
// case WireInfoPOD::WIRE_TYPE_LUTFF_COUT:
// return id("LUTFF_COUT");
// case WireInfoPOD::WIRE_TYPE_LUTFF_GLOBAL:
// return id("LUTFF_GLOBAL");
// case WireInfoPOD::WIRE_TYPE_CARRY_IN_MUX:
// return id("CARRY_IN_MUX");
// case WireInfoPOD::WIRE_TYPE_SP4_V:
// return id("SP4_V");
// case WireInfoPOD::WIRE_TYPE_SP4_H:
// return id("SP4_H");
// case WireInfoPOD::WIRE_TYPE_SP12_V:
// return id("SP12_V");
// case WireInfoPOD::WIRE_TYPE_SP12_H:
// return id("SP12_H");
// }
return IdString(); return IdString();
} }
@ -631,6 +567,7 @@ IdString Arch::getWireType(WireId wire) const
std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
{ {
std::vector<std::pair<IdString, std::string>> ret; std::vector<std::pair<IdString, std::string>> ret;
NPNR_ASSERT("TODO");
return ret; return ret;
} }
@ -664,27 +601,12 @@ IdString Arch::getPipName(PipId pip) const
std::stringstream pip_name; std::stringstream pip_name;
pip_name << ewi_src.mTileName << "." << ewi_src.mWireName << ".->." << ewi_dst.mWireName; pip_name << ewi_src.mTileName << "." << ewi_src.mWireName << ".->." << ewi_dst.mWireName;
return id(pip_name.str()); return id(pip_name.str());
//#if 1
// int x = chip_info->pip_data[pip.index].x;
// int y = chip_info->pip_data[pip.index].y;
//
// std::string src_name = chip_info->wire_data[chip_info->pip_data[pip.index].src].name.get();
// std::replace(src_name.begin(), src_name.end(), '/', '.');
//
// std::string dst_name = chip_info->wire_data[chip_info->pip_data[pip.index].dst].name.get();
// std::replace(dst_name.begin(), dst_name.end(), '/', '.');
//
// return id("X" + std::to_string(x) + "/Y" + std::to_string(y) + "/" + src_name + ".->." + dst_name);
//#else
// return id(chip_info->pip_data[pip.index].name.get());
//#endif
} }
std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const
{ {
std::vector<std::pair<IdString, std::string>> ret; std::vector<std::pair<IdString, std::string>> ret;
NPNR_ASSERT("TODO");
return ret; return ret;
} }
@ -694,11 +616,7 @@ BelId Arch::getPackagePinBel(const std::string &pin) const { return getBelByName
std::string Arch::getBelPackagePin(BelId bel) const std::string Arch::getBelPackagePin(BelId bel) const
{ {
// for (int i = 0; i < package_info->num_pins; i++) { NPNR_ASSERT("TODO");
// if (package_info->pins[i].bel_index == bel.index) {
// return std::string(package_info->pins[i].name.get());
// }
// }
return ""; return "";
} }
@ -717,39 +635,7 @@ IdString Arch::getGroupName(GroupId group) const
std::string suffix; std::string suffix;
switch (group.type) { switch (group.type) {
case GroupId::TYPE_FRAME: NPNR_ASSERT("TODO");
suffix = "tile";
break;
case GroupId::TYPE_MAIN_SW:
suffix = "main_sw";
break;
case GroupId::TYPE_LOCAL_SW:
suffix = "local_sw";
break;
case GroupId::TYPE_LC0_SW:
suffix = "lc0_sw";
break;
case GroupId::TYPE_LC1_SW:
suffix = "lc1_sw";
break;
case GroupId::TYPE_LC2_SW:
suffix = "lc2_sw";
break;
case GroupId::TYPE_LC3_SW:
suffix = "lc3_sw";
break;
case GroupId::TYPE_LC4_SW:
suffix = "lc4_sw";
break;
case GroupId::TYPE_LC5_SW:
suffix = "lc5_sw";
break;
case GroupId::TYPE_LC6_SW:
suffix = "lc6_sw";
break;
case GroupId::TYPE_LC7_SW:
suffix = "lc7_sw";
break;
default: default:
return IdString(); return IdString();
} }
@ -760,52 +646,7 @@ IdString Arch::getGroupName(GroupId group) const
std::vector<GroupId> Arch::getGroups() const std::vector<GroupId> Arch::getGroups() const
{ {
std::vector<GroupId> ret; std::vector<GroupId> ret;
/* NPNR_ASSERT("TODO");
for (int y = 0; y < chip_info->height; y++) {
for (int x = 0; x < chip_info->width; x++) {
TileType type = chip_info->tile_grid[y * chip_info->width + x];
if (type == TILE_NONE)
continue;
GroupId group;
group.type = GroupId::TYPE_FRAME;
group.x = x;
group.y = y;
// ret.push_back(group);
group.type = GroupId::TYPE_MAIN_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LOCAL_SW;
ret.push_back(group);
if (type == TILE_LOGIC) {
group.type = GroupId::TYPE_LC0_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC1_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC2_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC3_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC4_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC5_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC6_SW;
ret.push_back(group);
group.type = GroupId::TYPE_LC7_SW;
ret.push_back(group);
}
}
}*/
return ret; return ret;
} }
@ -824,12 +665,14 @@ std::vector<WireId> Arch::getGroupWires(GroupId group) const
std::vector<PipId> Arch::getGroupPips(GroupId group) const std::vector<PipId> Arch::getGroupPips(GroupId group) const
{ {
std::vector<PipId> ret; std::vector<PipId> ret;
NPNR_ASSERT("TODO");
return ret; return ret;
} }
std::vector<GroupId> Arch::getGroupGroups(GroupId group) const std::vector<GroupId> Arch::getGroupGroups(GroupId group) const
{ {
std::vector<GroupId> ret; std::vector<GroupId> ret;
NPNR_ASSERT("TODO");
return ret; return ret;
} }
@ -965,7 +808,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
} }
// TODO // TODO
// if (port == id_OMUX) // if (port == id_OMUX)
} else if (cell->type == id_IOB33) { } else if (cell->type == id_IOB33 || cell->type == id_IOB18) {
if (port == id_I) if (port == id_I)
return TMG_STARTPOINT; return TMG_STARTPOINT;
else if (port == id_O) else if (port == id_O)
@ -977,6 +820,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
} else if (cell->type == id_PS7) { } else if (cell->type == id_PS7) {
// TODO // TODO
return TMG_IGNORE; return TMG_IGNORE;
} else if (cell->type == id_MMCME2_ADV) {
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("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
} }
@ -1056,109 +901,3 @@ void Arch::assignCellInfo(CellInfo *cell)
} }
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END
// outside of any namespace
BOOST_SERIALIZATION_SPLIT_FREE(Segments::SegmentReference)
BOOST_SERIALIZATION_SPLIT_FREE(CompactSegmentIndex)
BOOST_SERIALIZATION_SPLIT_FREE(TileIndex)
BOOST_SERIALIZATION_SPLIT_FREE(Arc)
BOOST_SERIALIZATION_SPLIT_FREE(Tilewire)
BOOST_SERIALIZATION_SPLIT_FREE(WireIndex)
BOOST_SERIALIZATION_SPLIT_FREE(SiteIndex)
BOOST_SERIALIZATION_SPLIT_FREE(NEXTPNR_NAMESPACE::IdString)
namespace boost { namespace serialization {
template<class Archive>
inline void load_construct_data(
Archive & ar, NEXTPNR_NAMESPACE::TorcInfo * t, const unsigned int file_version
){
const auto& inDeviceName = static_cast<const nextpnr_binary_iarchive&>(ar).inDeviceName;
const auto& inPackageName = static_cast<const nextpnr_binary_iarchive&>(ar).inPackageName;
::new(t)NEXTPNR_NAMESPACE::TorcInfo(inDeviceName, inPackageName);
}
template<class Archive>
void save(Archive& ar, const Segments::SegmentReference& o, unsigned int) {
ar & o.getCompactSegmentIndex();
ar & o.getAnchorTileIndex();
}
template<class Archive>
void load(Archive& ar, Segments::SegmentReference& o, unsigned int) {
CompactSegmentIndex i;
TileIndex j;
ar & i;
ar & j;
o = Segments::SegmentReference(i, j);
}
#define SERIALIZE_POD(__T__) \
template<class Archive> \
void save(Archive& ar, const __T__& o, unsigned int) { \
ar & static_cast<__T__::pod>(o); \
} \
template<class Archive> \
void load(Archive& ar, __T__& o, unsigned int) { \
__T__::pod i; \
ar & i; \
o = __T__(i); \
}
SERIALIZE_POD(CompactSegmentIndex)
SERIALIZE_POD(TileIndex)
SERIALIZE_POD(WireIndex)
SERIALIZE_POD(SiteIndex)
template<class Archive>
void save(Archive& ar, const Arc& o, unsigned int) {
ar & o.getSourceTilewire();
ar & o.getSinkTilewire();
}
template<class Archive>
void load(Archive& ar, Arc& o, unsigned int) {
Tilewire s, t;
ar & s;
ar & t;
o = Arc(s, t);
}
template<class Archive>
void save(Archive& ar, const Tilewire& o, unsigned int) {
ar & o.getTileIndex();
ar & o.getWireIndex();
}
template<class Archive>
void load(Archive& ar, Tilewire& o, unsigned int) {
TileIndex i;
WireIndex j;
ar & i;
ar & j;
o.setTileIndex(TileIndex(i));
o.setWireIndex(WireIndex(j));
}
template<class Archive>
void serialize(Archive& ar, NEXTPNR_NAMESPACE::Loc& o, unsigned int) {
ar & o.x;
ar & o.y;
ar & o.z;
}
template<class Archive>
void serialize(Archive& ar, NEXTPNR_NAMESPACE::DelayInfo& o, unsigned int) {
ar & o.delay;
}
template<class Archive>
void save(Archive& ar, const NEXTPNR_NAMESPACE::IdString& o, unsigned int) {
const std::string i = o.str(static_cast<const nextpnr_binary_oarchive&>(ar).ctx);
ar & i;
}
template<class Archive>
void load(Archive& ar, NEXTPNR_NAMESPACE::IdString& o, unsigned int) {
std::string i;
ar & i;
o = static_cast<nextpnr_binary_iarchive&>(ar).ctx->id(i);
}
#define SERIALIZE_INDEX(__T__) \
template<class Archive> \
void serialize(Archive& ar, __T__& o, unsigned int) { \
ar & o.index; \
}
SERIALIZE_INDEX(NEXTPNR_NAMESPACE::BelId)
SERIALIZE_INDEX(NEXTPNR_NAMESPACE::WireId)
SERIALIZE_INDEX(NEXTPNR_NAMESPACE::PipId)
}} // namespace boost::serialization

View File

@ -21,8 +21,6 @@
#error Include "arch.h" via "nextpnr.h" only. #error Include "arch.h" via "nextpnr.h" only.
#endif #endif
#include <boost/serialization/access.hpp>
#include "torc/Architecture.hpp" #include "torc/Architecture.hpp"
#include "torc/Common.hpp" #include "torc/Common.hpp"
using namespace torc::architecture; using namespace torc::architecture;
@ -332,33 +330,12 @@ struct TorcInfo
std::vector<std::vector<PipId>> wire_to_pips_downhill; std::vector<std::vector<PipId>> wire_to_pips_downhill;
std::vector<Arc> pip_to_arc; std::vector<Arc> pip_to_arc;
int num_pips; int num_pips;
std::vector<WireId> pip_to_dst_wire;
int width; int width;
int height; int height;
std::vector<bool> wire_is_global;
std::vector<std::pair<int,int>> tile_to_xy;
TorcInfo(const std::string &inDeviceName, const std::string &inPackageName); TorcInfo(const std::string &inDeviceName, const std::string &inPackageName);
private:
friend class boost::serialization::access;
//TorcInfo(const std::string &inDeviceName, const std::string &inPackageName);
//template<class Archive, class T> friend inline void load_construct_data(Archive &ar, T *t, const unsigned int file_version);
template<class Archive>
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & bel_to_site_index;
ar & num_bels;
ar & site_index_to_bel;
ar & site_index_to_type;
ar & bel_to_loc;
ar & segment_to_wire;
ar & trivial_to_wire;
ar & wire_to_tilewire;
ar & num_wires;
ar & wire_to_delay;
ar & wire_to_pips_downhill;
ar & pip_to_arc;
ar & num_pips;
ar & pip_to_dst_wire;
}
}; };
extern std::unique_ptr<const TorcInfo> torc_info; extern std::unique_ptr<const TorcInfo> torc_info;
@ -500,7 +477,8 @@ struct ArchArgs
enum ArchArgsTypes enum ArchArgsTypes
{ {
NONE, NONE,
Z020 Z020,
VX980
} type = NONE; } type = NONE;
std::string package; std::string package;
}; };
@ -683,7 +661,6 @@ struct Arch : BaseCtx
auto pip = it->second.pip; auto pip = it->second.pip;
if (pip != PipId()) { if (pip != PipId()) {
pip_to_net[pip.index] = nullptr; pip_to_net[pip.index] = nullptr;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr;
} }
net_wires.erase(it); net_wires.erase(it);
@ -716,10 +693,7 @@ struct Arch : BaseCtx
BelPinRange getWireBelPins(WireId wire) const BelPinRange getWireBelPins(WireId wire) const
{ {
BelPinRange range; BelPinRange range;
// NPNR_ASSERT(wire != WireId()); // TODO
// range.b.ptr = chip_info->wire_data[wire.index].bel_pins.get();
// range.e.ptr = range.b.ptr + chip_info->wire_data[wire.index].num_bel_pins;
//throw;
return range; return range;
} }
@ -739,10 +713,8 @@ struct Arch : BaseCtx
{ {
NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip != PipId());
NPNR_ASSERT(pip_to_net[pip.index] == nullptr); NPNR_ASSERT(pip_to_net[pip.index] == nullptr);
// NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] == nullptr);
pip_to_net[pip.index] = net; pip_to_net[pip.index] = net;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = net;
WireId dst = getPipDstWire(pip); WireId dst = getPipDstWire(pip);
NPNR_ASSERT(wire_to_net[dst.index] == nullptr); NPNR_ASSERT(wire_to_net[dst.index] == nullptr);
@ -757,7 +729,6 @@ struct Arch : BaseCtx
{ {
NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip != PipId());
NPNR_ASSERT(pip_to_net[pip.index] != nullptr); NPNR_ASSERT(pip_to_net[pip.index] != nullptr);
// NPNR_ASSERT(switches_locked[chip_info->pip_data[pip.index].switch_index] != nullptr);
WireId dst = getPipDstWire(pip); WireId dst = getPipDstWire(pip);
NPNR_ASSERT(wire_to_net[dst.index] != nullptr); NPNR_ASSERT(wire_to_net[dst.index] != nullptr);
@ -765,7 +736,6 @@ struct Arch : BaseCtx
pip_to_net[pip.index]->wires.erase(dst); pip_to_net[pip.index]->wires.erase(dst);
pip_to_net[pip.index] = nullptr; pip_to_net[pip.index] = nullptr;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr;
refreshUiPip(pip); refreshUiPip(pip);
refreshUiWire(dst); refreshUiWire(dst);
} }
@ -773,25 +743,6 @@ struct Arch : BaseCtx
bool checkPipAvail(PipId pip) const bool checkPipAvail(PipId pip) const
{ {
NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip != PipId());
// auto &pi = chip_info->pip_data[pip.index];
// auto &si = chip_info->bits_info->switches[pi.switch_index];
// if (switches_locked[pi.switch_index] != nullptr)
// return false;
// if (pi.flags & PipInfoPOD::FLAG_ROUTETHRU) {
// NPNR_ASSERT(si.bel >= 0);
// if (bel_to_cell[si.bel] != nullptr)
// return false;
//}
// if (pi.flags & PipInfoPOD::FLAG_NOCARRY) {
// NPNR_ASSERT(si.bel >= 0);
// if (bel_carry[si.bel])
// return false;
//}
// return true;
return pip_to_net[pip.index] == nullptr; return pip_to_net[pip.index] == nullptr;
} }
@ -806,7 +757,6 @@ struct Arch : BaseCtx
NetInfo *getConflictingPipNet(PipId pip) const NetInfo *getConflictingPipNet(PipId pip) const
{ {
NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip != PipId());
// return switches_locked[chip_info->pip_data[pip.index].switch_index];
return pip_to_net[pip.index]; return pip_to_net[pip.index];
} }
@ -820,14 +770,8 @@ struct Arch : BaseCtx
Loc getPipLocation(PipId pip) const Loc getPipLocation(PipId pip) const
{ {
const auto &arc = torc_info->pip_to_arc[pip.index];
const auto &tw = arc.getSourceTilewire();
const auto &tile_info = torc_info->tiles.getTileInfo(tw.getTileIndex());
Loc loc; Loc loc;
loc.x = tile_info.getCol(); NPNR_ASSERT("TODO");
loc.y = tile_info.getRow();
loc.z = 0;
return loc; return loc;
} }
@ -850,7 +794,9 @@ struct Arch : BaseCtx
WireId getPipDstWire(PipId pip) const WireId getPipDstWire(PipId pip) const
{ {
NPNR_ASSERT(pip != PipId()); NPNR_ASSERT(pip != PipId());
return torc_info->pip_to_dst_wire[pip.index]; const auto &arc = torc_info->pip_to_arc[pip.index];
const auto &tw = arc.getSinkTilewire();
return torc_info->tilewire_to_wire(tw);
} }
DelayInfo getPipDelay(PipId pip) const DelayInfo getPipDelay(PipId pip) const
@ -985,6 +931,4 @@ struct Arch : BaseCtx
float placer_constraintWeight = 10; float placer_constraintWeight = 10;
}; };
// void ice40DelayFuzzerMain(Context *ctx);
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -29,43 +29,14 @@ NEXTPNR_NAMESPACE_BEGIN
bool Arch::logicCellsCompatible(const CellInfo **it, const size_t size) const bool Arch::logicCellsCompatible(const CellInfo **it, const size_t size) const
{ {
// bool dffs_exist = false, dffs_neg = false; // TODO: Check clock, clock-enable, and set-reset compatiility
// const NetInfo *cen = nullptr, *clk = nullptr, *sr = nullptr;
//
// for (auto cell : boost::make_iterator_range(it, it+size)) {
// NPNR_ASSERT(cell->belType == id_ICESTORM_LC);
// if (cell->lcInfo.dffEnable) {
// if (!dffs_exist) {
// dffs_exist = true;
// cen = cell->lcInfo.cen;
// clk = cell->lcInfo.clk;
// sr = cell->lcInfo.sr;
//
// if (cell->lcInfo.negClk) {
// dffs_neg = true;
// }
// } else {
// if (cen != cell->lcInfo.cen)
// return false;
// if (clk != cell->lcInfo.clk)
// return false;
// if (sr != cell->lcInfo.sr)
// return false;
// if (dffs_neg != cell->lcInfo.negClk)
// return false;
// }
// }
// locals_count += cell->lcInfo.inputCount;
// }
//
// return locals_count <= 32;
return true; return true;
} }
bool Arch::isBelLocationValid(BelId bel) const bool Arch::isBelLocationValid(BelId bel) const
{ {
if (getBelType(bel) == id_ICESTORM_LC) { if (getBelType(bel) == id("XC7_LC")) {
std::array<const CellInfo *, 8> bel_cells; std::array<const CellInfo *, 4> bel_cells;
size_t num_cells = 0; size_t num_cells = 0;
Loc bel_loc = getBelLocation(bel); Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
@ -85,70 +56,23 @@ bool Arch::isBelLocationValid(BelId bel) const
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
{ {
// if (cell->type == id_ICESTORM_LC) { if (cell->type == id("XC7_LC")) {
// NPNR_ASSERT(getBelType(bel) == id_ICESTORM_LC); std::array<const CellInfo *, 4> bel_cells;
// size_t num_cells = 0;
// std::array<const CellInfo *, 8> bel_cells;
// size_t num_cells = 0; Loc bel_loc = getBelLocation(bel);
// for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
// Loc bel_loc = getBelLocation(bel); CellInfo *ci_other = getBoundBelCell(bel_other);
// for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { if (ci_other != nullptr && bel_other != bel)
// CellInfo *ci_other = getBoundBelCell(bel_other); bel_cells[num_cells++] = ci_other;
// if (ci_other != nullptr && bel_other != bel) }
// bel_cells[num_cells++] = ci_other;
// } bel_cells[num_cells++] = cell;
// return logicCellsCompatible(bel_cells.data(), num_cells);
// bel_cells[num_cells++] = cell; }
// return logicCellsCompatible(bel_cells.data(), num_cells); else {
// } else if (cell->type == id_SB_IO) {
// // Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to.
//
// // Find shared PLL by looking for driving bel siblings from D_IN_0
// // that are a PLL clock output.
// auto wire = getBelPinWire(bel, id_D_IN_0);
// IdString pll_bel_pin;
// BelId pll_bel;
// for (auto pin : getWireBelPins(wire)) {
// if (pin.pin == id_PLLOUT_A || pin.pin == id_PLLOUT_B) {
// pll_bel = pin.bel;
// pll_bel_pin = pin.pin;
// break;
// }
// }
// // Is there a PLL that shares this IO buffer?
// if (pll_bel.index != -1) {
// auto pll_cell = getBoundBelCell(pll_bel);
// // Is a PLL placed in this PLL bel?
// if (pll_cell != nullptr) {
// // Is the shared port driving a net?
// auto pi = pll_cell->ports[pll_bel_pin];
// if (pi.net != nullptr) {
// // Are we perhaps a PAD INPUT Bel that can be placed here?
// if (pll_cell->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) {
// return true;
// }
// return false;
// }
// }
// }
// return getBelPackagePin(bel) != "";
// } else if (cell->type == id_SB_GB) {
// NPNR_ASSERT(cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net != nullptr);
// const NetInfo *net = cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net;
// IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT));
// int glb_id = std::stoi(std::string("") + glb_net.str(this).back());
// if (net->is_reset && net->is_enable)
// return false;
// else if (net->is_reset)
// return (glb_id % 2) == 0;
// else if (net->is_enable)
// return (glb_id % 2) == 1;
// else
// return true;
// } else {
// // TODO: IO cell clock checks
return true; return true;
// } }
} }
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

9
xc7/attosoc.pcf Normal file
View File

@ -0,0 +1,9 @@
COMP "led[0]" LOCATE = SITE "M14" LEVEL 1;
COMP "led[1]" LOCATE = SITE "M15" LEVEL 1;
COMP "led[2]" LOCATE = SITE "G14" LEVEL 1;
COMP "led[3]" LOCATE = SITE "D18" LEVEL 1;
COMP "clk" LOCATE = SITE "K17" LEVEL 1;
COMP "pll.mmcm_adv_inst" LOCATE = SITE "MMCME2_ADV_X1Y2" LEVEL 1;
NET "pll.clkin1" PERIOD = 8 nS ;
#PIN "clk_pin" = BEL "clk.PAD" PINNAME PAD;
#PIN "clk_pin" CLOCK_DEDICATED_ROUTE = FALSE;

12
xc7/attosoc.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
#set -ex
#rm -f picorv32.v
#wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v
yosys attosoc.ys
../nextpnr-xc7 --json attosoc.json --xdl attosoc.xdl --pcf attosoc.pcf --freq 150 |& tee attosoc.log
xdl -xdl2ncd attosoc.xdl
bitgen -w attosoc.ncd -g UnconstrainedPins:Allow
trce attosoc.ncd -v 10
netgen -sim -ofmt vhdl attosoc.ncd -w attosoc_pnr.vhd
ghdl -c -fexplicit --no-vital-checks --ieee=synopsys -Pxilinx-ise attosoc_tb.vhd attosoc_pnr.vhd -r testbench

127
xc7/attosoc.v Normal file
View File

@ -0,0 +1,127 @@
/*
* ECP5 PicoRV32 demo
*
* Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
* Copyright (C) 2018 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
`ifdef PICORV32_V
`error "attosoc.v must be read before picorv32.v!"
`endif
`define PICORV32_REGS picosoc_regs
module attosoc (
input clk,
output reg [7:0] led
);
reg [5:0] reset_cnt = 0;
wire resetn = &reset_cnt;
always @(posedge clk) begin
reset_cnt <= reset_cnt + !resetn;
end
parameter integer MEM_WORDS = 256;
parameter [31:0] STACKADDR = (4*MEM_WORDS); // end of memory
parameter [31:0] PROGADDR_RESET = 32'h 0000_0000; // ROM at 0x0
parameter integer ROM_BYTES = 256;
reg [7:0] rom [0:ROM_BYTES-1];
wire [31:0] rom_rdata = {rom[mem_addr+3], rom[mem_addr+2], rom[mem_addr+1], rom[mem_addr+0]};
initial $readmemh("firmware.hex", rom);
wire mem_valid;
wire mem_instr;
wire mem_ready;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
wire [31:0] mem_rdata;
wire rom_ready = mem_valid && mem_addr[31:24] == 8'h00;
wire iomem_valid;
wire iomem_ready;
wire [31:0] iomem_addr;
wire [31:0] iomem_wdata;
wire [3:0] iomem_wstrb;
wire [31:0] iomem_rdata;
assign iomem_valid = mem_valid && (mem_addr[31:24] > 8'h 01);
assign iomem_ready = 1'b1;
assign iomem_wstrb = mem_wstrb;
assign iomem_addr = mem_addr;
assign iomem_wdata = mem_wdata;
wire [31:0] spimemio_cfgreg_do;
always @(posedge clk)
if (iomem_valid && iomem_wstrb[0])
led <= iomem_wdata[7:0];
assign mem_ready = (iomem_valid && iomem_ready) || rom_ready;
assign mem_rdata = rom_rdata;
picorv32 #(
.STACKADDR(STACKADDR),
.PROGADDR_RESET(PROGADDR_RESET),
.PROGADDR_IRQ(32'h 0000_0000),
.BARREL_SHIFTER(0),
.COMPRESSED_ISA(0),
.ENABLE_MUL(0),
.ENABLE_DIV(0),
.ENABLE_IRQ(0),
.ENABLE_IRQ_QREGS(0)
) cpu (
.clk (clk ),
.resetn (resetn ),
.mem_valid (mem_valid ),
.mem_instr (mem_instr ),
.mem_ready (mem_ready ),
.mem_addr (mem_addr ),
.mem_wdata (mem_wdata ),
.mem_wstrb (mem_wstrb ),
.mem_rdata (mem_rdata )
);
endmodule
// Implementation note:
// Replace the following two modules with wrappers for your SRAM cells.
module picosoc_regs (
input clk, wen,
input [5:0] waddr,
input [5:0] raddr1,
input [5:0] raddr2,
input [31:0] wdata,
output [31:0] rdata1,
output [31:0] rdata2
);
reg [31:0] regs [0:31];
always @(posedge clk)
if (wen) regs[waddr[4:0]] <= wdata;
assign rdata1 = regs[raddr1[4:0]];
assign rdata2 = regs[raddr2[4:0]];
endmodule

56
xc7/attosoc.ys Normal file
View File

@ -0,0 +1,56 @@
read_verilog attosoc_top.v
read_verilog attosoc.v
read_verilog picorv32.v
read_verilog 125MHz_to_60MHz.v
#synth_xilinx -top picorv32
#begin:
read_verilog -lib +/xilinx/cells_sim.v
read_verilog -lib +/xilinx/cells_xtra.v
# read_verilog -lib +/xilinx/brams_bb.v
# read_verilog -lib +/xilinx/drams_bb.v
hierarchy -check -top top
#flatten: (only if -flatten)
proc
flatten
#coarse:
synth -run coarse
#bram:
# memory_bram -rules +/xilinx/brams.txt
# techmap -map +/xilinx/brams_map.v
#
#dram:
# memory_bram -rules +/xilinx/drams.txt
# techmap -map +/xilinx/drams_map.v
fine:
opt -fast -full
memory_map
dffsr2dff
# dff2dffe
opt -full
techmap -map +/techmap.v #-map +/xilinx/arith_map.v
opt -fast
map_luts:
abc -luts 2:2,3,6:5 #,10,20 [-dff]
clean
map_cells:
techmap -map +/xilinx/cells_map.v
dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT
clean
check:
hierarchy -check
stat
check -noinit
#edif: (only if -edif)
# write_edif <file-name>
write_json attosoc.json

25
xc7/attosoc_tb.vhd Normal file
View File

@ -0,0 +1,25 @@
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity testbench is
end entity;
architecture rtl of testbench is
signal clk : STD_LOGIC;
signal led : STD_LOGIC_VECTOR(3 downto 0);
begin
process begin
clk <= '0';
wait for 4 ns;
clk <= '1';
wait for 4 ns;
end process;
uut: entity work.name port map(clk_PAD_PAD => clk, led_0_OUTBUF_OUT => led(0), led_1_OUTBUF_OUT => led(1), led_2_OUTBUF_OUT => led(2), led_3_OUTBUF_OUT => led(3));
process
begin
report "led = " & std_logic'image(led(3)) & std_logic'image(led(2)) & std_logic'image(led(1)) & std_logic'image(led(0));
wait on led;
end process;
end rtl;

15
xc7/attosoc_top.v Normal file
View File

@ -0,0 +1,15 @@
module top (
input clk,
output [3:0] led
);
(* keep *)
wire led_unused;
wire gclk;
clk_wiz_v3_6 pll(.CLK_IN1(clk), .CLK_OUT1(gclk));
//assign gclk = clk;
attosoc soc(.clk(gclk), .led({led_unused, led}));
endmodule

View File

@ -3,6 +3,7 @@ COMP "led1" LOCATE = SITE "M15" LEVEL 1;
COMP "led2" LOCATE = SITE "G14" LEVEL 1; COMP "led2" LOCATE = SITE "G14" LEVEL 1;
COMP "led3" LOCATE = SITE "D18" LEVEL 1; COMP "led3" LOCATE = SITE "D18" LEVEL 1;
COMP "clki" LOCATE = SITE "K17" LEVEL 1; COMP "clki" LOCATE = SITE "K17" LEVEL 1;
COMP "clk_gb" LOCATE = SITE "BUFGCTRL_X0Y31" LEVEL 1;
NET "clki" PERIOD = 8 nS ; NET "clki" PERIOD = 8 nS ;
PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD; PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD;
PIN "clki_pin" CLOCK_DEDICATED_ROUTE = FALSE; PIN "clki_pin" CLOCK_DEDICATED_ROUTE = FALSE;

View File

@ -48,8 +48,6 @@ std::unique_ptr<CellInfo> create_xc7_cell(Context *ctx, IdString type, std::stri
new_cell->params[ctx->id("NEG_CLK")] = "0"; new_cell->params[ctx->id("NEG_CLK")] = "0";
new_cell->params[ctx->id("CARRY_ENABLE")] = "0"; new_cell->params[ctx->id("CARRY_ENABLE")] = "0";
new_cell->params[ctx->id("DFF_ENABLE")] = "0"; new_cell->params[ctx->id("DFF_ENABLE")] = "0";
new_cell->params[ctx->id("SET_NORESET")] = "0";
new_cell->params[ctx->id("ASYNC_SR")] = "0";
new_cell->params[ctx->id("CIN_CONST")] = "0"; new_cell->params[ctx->id("CIN_CONST")] = "0";
new_cell->params[ctx->id("CIN_SET")] = "0"; new_cell->params[ctx->id("CIN_SET")] = "0";
@ -70,186 +68,15 @@ std::unique_ptr<CellInfo> create_xc7_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "OMUX", PORT_OUT); add_port(ctx, new_cell.get(), "OMUX", PORT_OUT);
add_port(ctx, new_cell.get(), "COUT", PORT_OUT); add_port(ctx, new_cell.get(), "COUT", PORT_OUT);
} else if (type == ctx->id("IOBUF")) { } else if (type == ctx->id("IOBUF")) {
if (ctx->args.type == ArchArgs::Z020)
new_cell->type = id_IOB33; new_cell->type = id_IOB33;
new_cell->params[ctx->id("PIN_TYPE")] = "0"; else
new_cell->params[ctx->id("PULLUP")] = "0"; new_cell->type = id_IOB18;
new_cell->params[ctx->id("NEG_TRIGGER")] = "0";
new_cell->params[ctx->id("IOSTANDARD")] = "SB_LVCMOS";
// add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
//
// add_port(ctx, new_cell.get(), "LATCH_INPUT_VALUE", PORT_IN);
// add_port(ctx, new_cell.get(), "CLOCK_ENABLE", PORT_IN);
// add_port(ctx, new_cell.get(), "INPUT_CLK", PORT_IN);
// add_port(ctx, new_cell.get(), "OUTPUT_CLK", PORT_IN);
//
// add_port(ctx, new_cell.get(), "OUTPUT_ENABLE", PORT_IN);
// add_port(ctx, new_cell.get(), "D_OUT_0", PORT_IN);
// add_port(ctx, new_cell.get(), "D_OUT_1", PORT_IN);
//
// add_port(ctx, new_cell.get(), "D_IN_0", PORT_OUT);
// add_port(ctx, new_cell.get(), "D_IN_1", PORT_OUT);
add_port(ctx, new_cell.get(), "I", PORT_OUT); add_port(ctx, new_cell.get(), "I", PORT_OUT);
add_port(ctx, new_cell.get(), "O", PORT_IN); add_port(ctx, new_cell.get(), "O", PORT_IN);
// } else if (type == ctx->id("ICESTORM_RAM")) {
// new_cell->params[ctx->id("NEG_CLK_W")] = "0";
// new_cell->params[ctx->id("NEG_CLK_R")] = "0";
// new_cell->params[ctx->id("WRITE_MODE")] = "0";
// new_cell->params[ctx->id("READ_MODE")] = "0";
//
// add_port(ctx, new_cell.get(), "RCLK", PORT_IN);
// add_port(ctx, new_cell.get(), "RCLKE", PORT_IN);
// add_port(ctx, new_cell.get(), "RE", PORT_IN);
//
// add_port(ctx, new_cell.get(), "WCLK", PORT_IN);
// add_port(ctx, new_cell.get(), "WCLKE", PORT_IN);
// add_port(ctx, new_cell.get(), "WE", PORT_IN);
//
// for (int i = 0; i < 16; i++) {
// add_port(ctx, new_cell.get(), "WDATA_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "MASK_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "RDATA_" + std::to_string(i), PORT_OUT);
// }
//
// for (int i = 0; i < 11; i++) {
// add_port(ctx, new_cell.get(), "RADDR_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "WADDR_" + std::to_string(i), PORT_IN);
// }
// } else if (type == ctx->id("ICESTORM_LFOSC")) {
// add_port(ctx, new_cell.get(), "CLKLFEN", PORT_IN);
// add_port(ctx, new_cell.get(), "CLKLFPU", PORT_IN);
// add_port(ctx, new_cell.get(), "CLKLF", PORT_OUT);
// add_port(ctx, new_cell.get(), "CLKLF_FABRIC", PORT_OUT);
// } else if (type == ctx->id("ICESTORM_HFOSC")) {
// new_cell->params[ctx->id("CLKHF_DIV")] = "0b00";
// new_cell->params[ctx->id("TRIM_EN")] = "0b0";
//
// add_port(ctx, new_cell.get(), "CLKHFEN", PORT_IN);
// add_port(ctx, new_cell.get(), "CLKHFPU", PORT_IN);
// add_port(ctx, new_cell.get(), "CLKHF", PORT_OUT);
// add_port(ctx, new_cell.get(), "CLKHF_FABRIC", PORT_OUT);
// for (int i = 0; i < 10; i++)
// add_port(ctx, new_cell.get(), "TRIM" + std::to_string(i), PORT_IN);
} else if (type == id_BUFGCTRL) { } else if (type == id_BUFGCTRL) {
add_port(ctx, new_cell.get(), "I0", PORT_IN); add_port(ctx, new_cell.get(), "I0", PORT_IN);
add_port(ctx, new_cell.get(), "O", PORT_OUT); add_port(ctx, new_cell.get(), "O", PORT_OUT);
// } else if (type == ctx->id("ICESTORM_SPRAM")) {
// add_port(ctx, new_cell.get(), "WREN", PORT_IN);
// add_port(ctx, new_cell.get(), "CHIPSELECT", PORT_IN);
// add_port(ctx, new_cell.get(), "CLOCK", PORT_IN);
// add_port(ctx, new_cell.get(), "STANDBY", PORT_IN);
// add_port(ctx, new_cell.get(), "SLEEP", PORT_IN);
// add_port(ctx, new_cell.get(), "POWEROFF", PORT_IN);
//
// for (int i = 0; i < 16; i++) {
// add_port(ctx, new_cell.get(), "DATAIN_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "DATAOUT_" + std::to_string(i), PORT_OUT);
// }
// for (int i = 0; i < 14; i++) {
// add_port(ctx, new_cell.get(), "ADDRESS_" + std::to_string(i), PORT_IN);
// }
// for (int i = 0; i < 4; i++) {
// add_port(ctx, new_cell.get(), "MASKWREN_" + std::to_string(i), PORT_IN);
// }
// } else if (type == ctx->id("ICESTORM_DSP")) {
// new_cell->params[ctx->id("NEG_TRIGGER")] = "0";
//
// new_cell->params[ctx->id("C_REG")] = "0";
// new_cell->params[ctx->id("A_REG")] = "0";
// new_cell->params[ctx->id("B_REG")] = "0";
// new_cell->params[ctx->id("D_REG")] = "0";
// new_cell->params[ctx->id("TOP_8x8_MULT_REG")] = "0";
// new_cell->params[ctx->id("BOT_8x8_MULT_REG")] = "0";
// new_cell->params[ctx->id("PIPELINE_16x16_MULT_REG1")] = "0";
// new_cell->params[ctx->id("PIPELINE_16x16_MULT_REG2")] = "0";
//
// new_cell->params[ctx->id("TOPOUTPUT_SELECT")] = "0";
// new_cell->params[ctx->id("TOPADDSUB_LOWERINPUT")] = "0";
// new_cell->params[ctx->id("TOPADDSUB_UPPERINPUT")] = "0";
// new_cell->params[ctx->id("TOPADDSUB_CARRYSELECT")] = "0";
//
// new_cell->params[ctx->id("BOTOUTPUT_SELECT")] = "0";
// new_cell->params[ctx->id("BOTADDSUB_LOWERINPUT")] = "0";
// new_cell->params[ctx->id("BOTADDSUB_UPPERINPUT")] = "0";
// new_cell->params[ctx->id("BOTADDSUB_CARRYSELECT")] = "0";
//
// new_cell->params[ctx->id("MODE_8x8")] = "0";
// new_cell->params[ctx->id("A_SIGNED")] = "0";
// new_cell->params[ctx->id("B_SIGNED")] = "0";
//
// add_port(ctx, new_cell.get(), "CLK", PORT_IN);
// add_port(ctx, new_cell.get(), "CE", PORT_IN);
// for (int i = 0; i < 16; i++) {
// add_port(ctx, new_cell.get(), "C_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "A_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "B_" + std::to_string(i), PORT_IN);
// add_port(ctx, new_cell.get(), "D_" + std::to_string(i), PORT_IN);
// }
// add_port(ctx, new_cell.get(), "AHOLD", PORT_IN);
// add_port(ctx, new_cell.get(), "BHOLD", PORT_IN);
// add_port(ctx, new_cell.get(), "CHOLD", PORT_IN);
// add_port(ctx, new_cell.get(), "DHOLD", PORT_IN);
//
// add_port(ctx, new_cell.get(), "IRSTTOP", PORT_IN);
// add_port(ctx, new_cell.get(), "IRSTBOT", PORT_IN);
// add_port(ctx, new_cell.get(), "ORSTTOP", PORT_IN);
// add_port(ctx, new_cell.get(), "ORSTBOT", PORT_IN);
//
// add_port(ctx, new_cell.get(), "OLOADTOP", PORT_IN);
// add_port(ctx, new_cell.get(), "OLOADBOT", PORT_IN);
//
// add_port(ctx, new_cell.get(), "ADDSUBTOP", PORT_IN);
// add_port(ctx, new_cell.get(), "ADDSUBBOT", PORT_IN);
//
// add_port(ctx, new_cell.get(), "OHOLDTOP", PORT_IN);
// add_port(ctx, new_cell.get(), "OHOLDBOT", PORT_IN);
//
// add_port(ctx, new_cell.get(), "CI", PORT_IN);
// add_port(ctx, new_cell.get(), "ACCUMCI", PORT_IN);
// add_port(ctx, new_cell.get(), "SIGNEXTIN", PORT_IN);
//
// for (int i = 0; i < 32; i++) {
// add_port(ctx, new_cell.get(), "O_" + std::to_string(i), PORT_OUT);
// }
//
// add_port(ctx, new_cell.get(), "CO", PORT_OUT);
// add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT);
// add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT);
//
// } else if (type == ctx->id("ICESTORM_PLL")) {
// new_cell->params[ctx->id("DELAY_ADJMODE_FB")] = "0";
// new_cell->params[ctx->id("DELAY_ADJMODE_REL")] = "0";
//
// new_cell->params[ctx->id("DIVF")] = "0";
// new_cell->params[ctx->id("DIVQ")] = "0";
// new_cell->params[ctx->id("DIVR")] = "0";
//
// new_cell->params[ctx->id("FDA_FEEDBACK")] = "0";
// new_cell->params[ctx->id("FDA_RELATIVE")] = "0";
// new_cell->params[ctx->id("FEEDBACK_PATH")] = "0";
// new_cell->params[ctx->id("FILTER_RANGE")] = "0";
//
// new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0";
// new_cell->params[ctx->id("PLLOUT_SELECT_B")] = "0";
//
// new_cell->params[ctx->id("PLLTYPE")] = "0";
// new_cell->params[ctx->id("SHIFTREG_DIVMODE")] = "0";
// new_cell->params[ctx->id("TEST_MODE")] = "0";
//
// add_port(ctx, new_cell.get(), "BYPASS", PORT_IN);
// add_port(ctx, new_cell.get(), "DYNAMICDELAY", PORT_IN);
// add_port(ctx, new_cell.get(), "EXTFEEDBACK", PORT_IN);
// add_port(ctx, new_cell.get(), "LATCHINPUTVALUE", PORT_IN);
// add_port(ctx, new_cell.get(), "REFERENCECLK", PORT_IN);
// add_port(ctx, new_cell.get(), "RESETB", PORT_IN);
//
// add_port(ctx, new_cell.get(), "SCLK", PORT_IN);
// add_port(ctx, new_cell.get(), "SDI", PORT_IN);
// add_port(ctx, new_cell.get(), "SDI", PORT_OUT);
//
// add_port(ctx, new_cell.get(), "LOCK", PORT_OUT);
// add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT);
// add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT);
} else { } else {
log_error("unable to create XC7 cell of type %s\n", type.c_str(ctx)); log_error("unable to create XC7 cell of type %s\n", type.c_str(ctx));
} }
@ -259,17 +86,18 @@ std::unique_ptr<CellInfo> create_xc7_cell(Context *ctx, IdString type, std::stri
void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
{ {
lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")]; lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")];
replace_port(lut, ctx->id("I0"), lc, id_I6); int i = 6;
if (get_net_or_empty(lut, id_I1))
replace_port(lut, id_I1, lc, id_I5);
if (get_net_or_empty(lut, id_I2))
replace_port(lut, id_I2, lc, id_I4);
if (get_net_or_empty(lut, id_I3))
replace_port(lut, id_I3, lc, id_I3);
if (get_net_or_empty(lut, id_I4))
replace_port(lut, id_I4, lc, id_I2);
if (get_net_or_empty(lut, id_I5)) if (get_net_or_empty(lut, id_I5))
replace_port(lut, id_I5, lc, id_I1); replace_port(lut, id_I5, lc, ctx->id("I" + std::to_string(i--)));
if (get_net_or_empty(lut, id_I4))
replace_port(lut, id_I4, lc, ctx->id("I" + std::to_string(i--)));
if (get_net_or_empty(lut, id_I3))
replace_port(lut, id_I3, lc, ctx->id("I" + std::to_string(i--)));
if (get_net_or_empty(lut, id_I2))
replace_port(lut, id_I2, lc, ctx->id("I" + std::to_string(i--)));
if (get_net_or_empty(lut, id_I1))
replace_port(lut, id_I1, lc, ctx->id("I" + std::to_string(i--)));
replace_port(lut, ctx->id("I0"), lc, ctx->id("I" + std::to_string(i--)));
if (no_dff) { if (no_dff) {
replace_port(lut, id_O, lc, id_O); replace_port(lut, id_O, lc, id_O);
lc->params[ctx->id("DFF_ENABLE")] = "0"; lc->params[ctx->id("DFF_ENABLE")] = "0";
@ -282,43 +110,65 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l
lc->params[ctx->id("DFF_ENABLE")] = "1"; lc->params[ctx->id("DFF_ENABLE")] = "1";
std::string config = dff->type.str(ctx).substr(2); std::string config = dff->type.str(ctx).substr(2);
auto citer = config.begin(); auto citer = config.begin();
replace_port(dff, ctx->id("C"), lc, ctx->id("CLK")); replace_port(dff, ctx->id("C"), lc, id_CLK);
if (citer != config.end()) { if (citer != config.end()) {
if (*citer == 'C' || *citer == 'P') auto gnd_net = ctx->nets.at(ctx->id("$PACKER_GND_NET")).get();
lc->params[ctx->id("ASYNC_SR")] = "1";
else
lc->params[ctx->id("ASYNC_SR")] = "0";
if (*citer == 'S') { if (*citer == 'S') {
citer++; citer++;
replace_port(dff, ctx->id("S"), lc, ctx->id("SR")); if (get_net_or_empty(dff, id_S) != gnd_net) {
lc->params[ctx->id("SET_NORESET")] = "1"; lc->params[id_SR] = "SRHIGH";
lc->params[ctx->id("SYNC_ATTR")] = "SYNC";
replace_port(dff, id_S, lc, id_SR);
}
else
disconnect_port(ctx, dff, id_S);
} else if (*citer == 'R') { } else if (*citer == 'R') {
citer++; citer++;
replace_port(dff, ctx->id("R"), lc, ctx->id("SR")); if (get_net_or_empty(dff, id_R) != gnd_net) {
lc->params[ctx->id("SET_NORESET")] = "0"; lc->params[id_SR] = "SRLOW";
lc->params[ctx->id("SYNC_ATTR")] = "SYNC";
replace_port(dff, id_R, lc, id_SR);
}
else
disconnect_port(ctx, dff, id_R);
} else if (*citer == 'C') { } else if (*citer == 'C') {
citer++; citer++;
replace_port(dff, ctx->id("CLR"), lc, ctx->id("SR")); if (get_net_or_empty(dff, id_CLR) != gnd_net) {
lc->params[ctx->id("SET_NORESET")] = "0"; lc->params[id_SR] = "SRLOW";
lc->params[ctx->id("SYNC_ATTR")] = "ASYNC";
replace_port(dff, id_CLR, lc, id_SR);
}
else
disconnect_port(ctx, dff, id_CLR);
} else { } else {
NPNR_ASSERT(*citer == 'P'); NPNR_ASSERT(*citer == 'P');
citer++; citer++;
replace_port(dff, ctx->id("PRE"), lc, ctx->id("SR")); if (get_net_or_empty(dff, id_PRE) != gnd_net) {
lc->params[ctx->id("SET_NORESET")] = "1"; lc->params[id_SR] = "SRHIGH";
lc->params[ctx->id("SYNC_ATTR")] = "ASYNC";
replace_port(dff, id_PRE, lc, id_SR);
}
else
disconnect_port(ctx, dff, id_PRE);
} }
} }
if (citer != config.end() && *citer == 'E') { if (citer != config.end() && *citer == 'E') {
replace_port(dff, ctx->id("CE"), lc, ctx->id("CE")); auto vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
++citer; ++citer;
if (get_net_or_empty(dff, ctx->id("CE")) != vcc_net)
replace_port(dff, ctx->id("CE"), lc, ctx->id("CE"));
else
disconnect_port(ctx, dff, ctx->id("CE"));
} }
NPNR_ASSERT(citer == config.end()); NPNR_ASSERT(citer == config.end());
if (pass_thru_lut) { if (pass_thru_lut) {
lc->params[ctx->id("INIT")] = "1"; lc->params[ctx->id("INIT")] = "2";
replace_port(dff, ctx->id("D"), lc, id_I1); replace_port(dff, ctx->id("D"), lc, id_I1);
} }
@ -326,7 +176,7 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l
auto it = dff->params.find(ctx->id("INIT")); auto it = dff->params.find(ctx->id("INIT"));
if (it != dff->params.end()) if (it != dff->params.end())
lc->params[ctx->id("DFF_INIT")] = it->second; lc->params[ctx->id("FFINIT")] = it->second == "1" ? "INIT1" : "INIT0";
} }
void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio) void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)
@ -365,34 +215,11 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)
} }
} }
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell)
{
if (cell->type == ctx->id("SB_PLL40_PAD"))
return 2;
if (cell->type == ctx->id("SB_PLL40_2_PAD"))
return 4;
if (cell->type == ctx->id("SB_PLL40_2F_PAD"))
return 5;
if (cell->type == ctx->id("SB_PLL40_CORE"))
return 3;
if (cell->type == ctx->id("SB_PLL40_2F_CORE"))
return 7;
NPNR_ASSERT(0);
}
bool is_clock_port(const BaseCtx *ctx, const PortRef &port) bool is_clock_port(const BaseCtx *ctx, const PortRef &port)
{ {
if (port.cell == nullptr) if (port.cell == nullptr)
return false; return false;
if (is_ff(ctx, port.cell)) NPNR_ASSERT("TODO");
return port.port == ctx->id("C");
if (port.cell->type == ctx->id("ICESTORM_LC"))
return port.port == ctx->id("CLK");
if (is_ram(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_RAM"))
return port.port == ctx->id("RCLK") || port.port == ctx->id("WCLK") || port.port == ctx->id("RCLKN") ||
port.port == ctx->id("WCLKN");
if (is_sb_mac16(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_DSP"))
return port.port == ctx->id("CLK");
return false; return false;
} }
@ -400,13 +227,7 @@ bool is_reset_port(const BaseCtx *ctx, const PortRef &port)
{ {
if (port.cell == nullptr) if (port.cell == nullptr)
return false; return false;
if (is_ff(ctx, port.cell)) NPNR_ASSERT("TODO");
return port.port == ctx->id("R") || port.port == ctx->id("S");
if (port.cell->type == ctx->id("ICESTORM_LC"))
return port.port == ctx->id("SR");
if (is_sb_mac16(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_DSP"))
return port.port == ctx->id("IRSTTOP") || port.port == ctx->id("IRSTBOT") || port.port == ctx->id("ORSTTOP") ||
port.port == ctx->id("ORSTBOT");
return false; return false;
} }
@ -414,13 +235,7 @@ bool is_enable_port(const BaseCtx *ctx, const PortRef &port)
{ {
if (port.cell == nullptr) if (port.cell == nullptr)
return false; return false;
if (is_ff(ctx, port.cell)) NPNR_ASSERT("TODO");
return port.port == ctx->id("E");
if (port.cell->type == ctx->id("ICESTORM_LC"))
return port.port == ctx->id("CEN");
// FIXME
// if (is_sb_mac16(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_DSP"))
// return port.port == ctx->id("CE");
return false; return false;
} }

View File

@ -14,6 +14,10 @@ X(COUT)
X(CEN) X(CEN)
X(CLK) X(CLK)
X(SR) X(SR)
X(S)
X(R)
X(PRE)
X(CLR)
X(MASK_0) X(MASK_0)
X(MASK_1) X(MASK_1)
@ -458,4 +462,6 @@ X(FDPE)
X(BUFGCTRL) X(BUFGCTRL)
X(SLICE_LUT6) X(SLICE_LUT6)
X(IOB33) X(IOB33)
X(IOB18)
X(PS7) X(PS7)
X(MMCME2_ADV)

View File

@ -25,89 +25,16 @@ NEXTPNR_NAMESPACE_BEGIN
#define NUM_FUZZ_ROUTES 100000 #define NUM_FUZZ_ROUTES 100000
void ice40DelayFuzzerMain(Context *ctx)
{
// std::vector<WireId> srcWires, dstWires;
//
// for (int i = 0; i < ctx->chip_info->num_wires; i++) {
// WireId wire;
// wire.index = i;
//
// switch (ctx->chip_info->wire_data[i].type) {
// case WireInfoPOD::WIRE_TYPE_LUTFF_OUT:
// srcWires.push_back(wire);
// break;
//
// case WireInfoPOD::WIRE_TYPE_LUTFF_IN_LUT:
// dstWires.push_back(wire);
// break;
//
// default:
// break;
// }
// }
//
// ctx->shuffle(srcWires);
// ctx->shuffle(dstWires);
//
// int index = 0;
// int cnt = 0;
//
// while (cnt < NUM_FUZZ_ROUTES) {
// if (index >= int(srcWires.size()) || index >= int(dstWires.size())) {
// index = 0;
// ctx->shuffle(srcWires);
// ctx->shuffle(dstWires);
// }
//
// WireId src = srcWires[index];
// WireId dst = dstWires[index++];
// std::unordered_map<WireId, PipId> route;
//
//#if NUM_FUZZ_ROUTES <= 1000
// if (!ctx->getActualRouteDelay(src, dst, nullptr, &route, false))
// continue;
//#else
// if (!ctx->getActualRouteDelay(src, dst, nullptr, &route, true))
// continue;
//#endif
//
// WireId cursor = dst;
// delay_t delay = 0;
//
// while (1) {
// delay += ctx->getWireDelay(cursor).maxDelay();
//
// printf("%s %d %d %s %s %d %d\n", cursor == dst ? "dst" : "src",
// int(ctx->chip_info->wire_data[cursor.index].x), int(ctx->chip_info->wire_data[cursor.index].y),
// ctx->getWireType(cursor).c_str(ctx), ctx->getWireName(cursor).c_str(ctx), int(delay),
// int(ctx->estimateDelay(cursor, dst)));
//
// if (cursor == src)
// break;
//
// PipId pip = route.at(cursor);
// delay += ctx->getPipDelay(pip).maxDelay();
// cursor = ctx->getPipSrcWire(pip);
// }
//
// cnt++;
//
// if (cnt % 100 == 0)
// fprintf(stderr, "Fuzzed %d arcs.\n", cnt);
// }
}
delay_t Arch::estimateDelay(WireId src, WireId dst) const delay_t Arch::estimateDelay(WireId src, WireId dst) const
{ {
const auto &src_tw = torc_info->wire_to_tilewire[src.index]; const auto &src_tw = torc_info->wire_to_tilewire[src.index];
const auto &src_info = torc_info->tiles.getTileInfo(src_tw.getTileIndex()); const auto &src_loc = torc_info->tile_to_xy[src_tw.getTileIndex()];
const auto &dst_tw = torc_info->wire_to_tilewire[dst.index]; const auto &dst_tw = torc_info->wire_to_tilewire[dst.index];
const auto &dst_info = torc_info->tiles.getTileInfo(dst_tw.getTileIndex()); const auto &dst_loc = torc_info->tile_to_xy[dst_tw.getTileIndex()];
auto abs_delta_x = (abs(src_info.getCol() - dst_info.getCol()) + 1) /
2; // Divide by 2 because XDL coordinate space counts the INT tiles between CLBs if (!torc_info->wire_is_global[src.index]) {
auto abs_delta_y = abs(src_info.getRow() - dst_info.getRow()); auto abs_delta_x = abs(dst_loc.first - src_loc.first);
#if 1 auto abs_delta_y = abs(dst_loc.second - src_loc.second);
auto div_LH = std::div(abs_delta_x, 12); auto div_LH = std::div(abs_delta_x, 12);
auto div_LV = std::div(abs_delta_y, 18); auto div_LV = std::div(abs_delta_y, 18);
auto div_LVB = std::div(div_LV.rem, 12); auto div_LVB = std::div(div_LV.rem, 12);
@ -122,9 +49,14 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 + return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 +
(div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 + (div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 +
(num_H1 + num_V1) * 150; (num_H1 + num_V1) * 150;
#else }
return std::max(150, 33 * abs_delta_x + 66 * abs_delta_y); else {
#endif auto src_y = src_loc.second;
auto dst_y = dst_loc.second;
auto div_src_y = std::div(src_y, 52);
auto div_dst_y = std::div(dst_y, 52);
return abs(div_dst_y.quot - div_src_y.quot) * 52 + abs(div_dst_y.rem - div_src_y.rem);
}
} }
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
@ -134,7 +66,6 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
auto sink_loc = getBelLocation(sink.cell->bel); auto sink_loc = getBelLocation(sink.cell->bel);
auto abs_delta_x = abs(driver_loc.x - sink_loc.x); auto abs_delta_x = abs(driver_loc.x - sink_loc.x);
auto abs_delta_y = abs(driver_loc.y - sink_loc.y); auto abs_delta_y = abs(driver_loc.y - sink_loc.y);
#if 1
auto div_LH = std::div(abs_delta_x, 12); auto div_LH = std::div(abs_delta_x, 12);
auto div_LV = std::div(abs_delta_y, 18); auto div_LV = std::div(abs_delta_y, 18);
auto div_LVB = std::div(div_LV.rem, 12); auto div_LVB = std::div(div_LV.rem, 12);
@ -149,9 +80,6 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 + return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 +
(div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 + (div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 +
(num_H1 + num_V1) * 150; (num_H1 + num_V1) * 150;
#else
return std::max(150, 33 * abs_delta_x + 66 * abs_delta_y);
#endif
} }
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

8
xc7/firmware.hex Normal file
View File

@ -0,0 +1,8 @@
@00000000
13 04 20 00 B7 04 00 02 93 09 00 10 13 04 14 00
63 44 34 01 13 04 20 00 13 09 20 00 63 5E 89 00
13 05 04 00 93 05 09 00 EF 00 C0 01 63 0A 05 00
13 09 19 00 6F F0 9F FE 23 A0 84 00 EF 00 80 01
6F F0 DF FC 93 02 10 00 33 05 B5 40 E3 5E 55 FE
67 80 00 00 B7 82 05 00 93 82 02 E4 93 82 F2 FF
E3 9E 02 FE 67 80 00 00

View File

@ -51,8 +51,9 @@ Xc7CommandHandler::Xc7CommandHandler(int argc, char **argv) : CommandHandler(arg
po::options_description Xc7CommandHandler::getArchOptions() po::options_description Xc7CommandHandler::getArchOptions()
{ {
po::options_description specific("Architecture specific options"); po::options_description specific("Architecture specific options");
specific.add_options()("xc7z020", "set device type to xc7z020"); specific.add_options()("z020", "set device type to xc7z020");
// specific.add_options()("package", po::value<std::string>(), "set device package"); specific.add_options()("vx980", "set device type to xc7v980");
specific.add_options()("package", po::value<std::string>(), "set device package");
specific.add_options()("pcf", po::value<std::string>(), "PCF constraints file to ingest"); specific.add_options()("pcf", po::value<std::string>(), "PCF constraints file to ingest");
specific.add_options()("xdl", po::value<std::string>(), "XDL file to write"); specific.add_options()("xdl", po::value<std::string>(), "XDL file to write");
// specific.add_options()("tmfuzz", "run path delay estimate fuzzer"); // specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
@ -97,6 +98,12 @@ std::unique_ptr<Context> Xc7CommandHandler::createContext()
chipArgs.package = "clg400"; chipArgs.package = "clg400";
} }
if (vm.count("vx980")) {
chipArgs.type = ArchArgs::VX980;
chipArgs.package = "ffg1926";
}
if (chipArgs.type == ArchArgs::NONE) { if (chipArgs.type == ArchArgs::NONE) {
chipArgs.type = ArchArgs::Z020; chipArgs.type = ArchArgs::Z020;
chipArgs.package = "clg400"; chipArgs.package = "clg400";

View File

@ -128,172 +128,15 @@ static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value)
// Pack carry logic // Pack carry logic
static void pack_carries(Context *ctx) static void pack_carries(Context *ctx)
{ {
log_info("Packing carries..\n"); //log_info("Packing carries..\n");
std::unordered_set<IdString> exhausted_cells; // TODO
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_carry(ctx, ci)) {
packed_cells.insert(cell.first);
CellInfo *carry_ci_lc;
bool ci_value;
bool ci_const = net_is_constant(ctx, ci->ports.at(ctx->id("CI")).net, ci_value);
if (ci_const) {
carry_ci_lc = nullptr;
} else {
carry_ci_lc = net_only_drives(ctx, ci->ports.at(ctx->id("CI")).net, is_lc, ctx->id("I3"), false);
}
std::set<IdString> i0_matches, i1_matches;
NetInfo *i0_net = ci->ports.at(ctx->id("I0")).net;
NetInfo *i1_net = ci->ports.at(ctx->id("I1")).net;
// Find logic cells connected to both I0 and I1
if (i0_net) {
for (auto usr : i0_net->users) {
if (is_lc(ctx, usr.cell) && usr.port == ctx->id("I1")) {
if (ctx->cells.find(usr.cell->name) != ctx->cells.end() &&
exhausted_cells.find(usr.cell->name) == exhausted_cells.end()) {
// This clause stops us double-packing cells
i0_matches.insert(usr.cell->name);
if (!i1_net) {
// I1 is don't care when disconnected, duplicate I0
i1_matches.insert(usr.cell->name);
}
}
}
}
}
if (i1_net) {
for (auto usr : i1_net->users) {
if (is_lc(ctx, usr.cell) && usr.port == ctx->id("I2")) {
if (ctx->cells.find(usr.cell->name) != ctx->cells.end() &&
exhausted_cells.find(usr.cell->name) == exhausted_cells.end()) {
// This clause stops us double-packing cells
i1_matches.insert(usr.cell->name);
if (!i0_net) {
// I0 is don't care when disconnected, duplicate I1
i0_matches.insert(usr.cell->name);
}
}
}
}
}
std::set<IdString> carry_lcs;
std::set_intersection(i0_matches.begin(), i0_matches.end(), i1_matches.begin(), i1_matches.end(),
std::inserter(carry_lcs, carry_lcs.end()));
CellInfo *carry_lc = nullptr;
if (carry_ci_lc && carry_lcs.find(carry_ci_lc->name) != carry_lcs.end()) {
carry_lc = carry_ci_lc;
} else {
// No LC to pack into matching I0/I1, insert a new one
std::unique_ptr<CellInfo> created_lc =
create_xc7_cell(ctx, ctx->id("XC7_LC"), cell.first.str(ctx) + "$CARRY");
carry_lc = created_lc.get();
created_lc->ports.at(ctx->id("I1")).net = i0_net;
if (i0_net) {
PortRef pr;
pr.cell = created_lc.get();
pr.port = ctx->id("I1");
i0_net->users.push_back(pr);
}
created_lc->ports.at(ctx->id("I2")).net = i1_net;
if (i1_net) {
PortRef pr;
pr.cell = created_lc.get();
pr.port = ctx->id("I2");
i1_net->users.push_back(pr);
}
new_cells.push_back(std::move(created_lc));
}
carry_lc->params[ctx->id("CARRY_ENABLE")] = "1";
replace_port(ci, ctx->id("CI"), carry_lc, ctx->id("CIN"));
replace_port(ci, ctx->id("CO"), carry_lc, ctx->id("COUT"));
if (i0_net) {
auto &i0_usrs = i0_net->users;
i0_usrs.erase(std::remove_if(i0_usrs.begin(), i0_usrs.end(), [ci, ctx](const PortRef &pr) {
return pr.cell == ci && pr.port == ctx->id("I0");
}));
}
if (i1_net) {
auto &i1_usrs = i1_net->users;
i1_usrs.erase(std::remove_if(i1_usrs.begin(), i1_usrs.end(), [ci, ctx](const PortRef &pr) {
return pr.cell == ci && pr.port == ctx->id("I1");
}));
}
// Check for constant driver on CIN
if (carry_lc->ports.at(ctx->id("CIN")).net != nullptr) {
IdString cin_net = carry_lc->ports.at(ctx->id("CIN")).net->name;
if (cin_net == ctx->id("$PACKER_GND_NET") || cin_net == ctx->id("$PACKER_VCC_NET")) {
carry_lc->params[ctx->id("CIN_CONST")] = "1";
carry_lc->params[ctx->id("CIN_SET")] = cin_net == ctx->id("$PACKER_VCC_NET") ? "1" : "0";
carry_lc->ports.at(ctx->id("CIN")).net = nullptr;
auto &cin_users = ctx->nets.at(cin_net)->users;
cin_users.erase(
std::remove_if(cin_users.begin(), cin_users.end(), [carry_lc, ctx](const PortRef &pr) {
return pr.cell == carry_lc && pr.port == ctx->id("CIN");
}));
}
}
exhausted_cells.insert(carry_lc->name);
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
} }
// "Pack" RAMs // "Pack" RAMs
static void pack_ram(Context *ctx) static void pack_ram(Context *ctx)
{ {
log_info("Packing RAMs..\n"); //log_info("Packing RAMs..\n");
// TODO
std::unordered_set<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_ram(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_RAM"), ci->name.str(ctx) + "_RAM");
packed_cells.insert(ci->name);
for (auto param : ci->params)
packed->params[param.first] = param.second;
packed->params[ctx->id("NEG_CLK_W")] =
std::to_string(ci->type == ctx->id("SB_RAM40_4KNW") || ci->type == ctx->id("SB_RAM40_4KNRNW"));
packed->params[ctx->id("NEG_CLK_R")] =
std::to_string(ci->type == ctx->id("SB_RAM40_4KNR") || ci->type == ctx->id("SB_RAM40_4KNRNW"));
packed->type = ctx->id("ICESTORM_RAM");
for (auto port : ci->ports) {
PortInfo &pi = port.second;
std::string newname = pi.name.str(ctx);
size_t bpos = newname.find('[');
if (bpos != std::string::npos) {
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2);
}
if (pi.name == ctx->id("RCLKN"))
newname = "RCLK";
else if (pi.name == ctx->id("WCLKN"))
newname = "WCLK";
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
new_cells.push_back(std::move(packed));
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
} }
// Merge a net into a constant net // Merge a net into a constant net
@ -308,14 +151,6 @@ static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constne
if ((is_lut(ctx, uc) || is_lc(ctx, uc) || is_carry(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && if ((is_lut(ctx, uc) || is_lc(ctx, uc) || is_carry(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') &&
!constval) { !constval) {
uc->ports[user.port].net = nullptr; uc->ports[user.port].net = nullptr;
} else if ((is_sb_mac16(ctx, uc) || uc->type == ctx->id("ICESTORM_DSP")) &&
(user.port != ctx->id("CLK") &&
((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) {
uc->ports[user.port].net = nullptr;
} else if (is_ram(ctx, uc) && !constval && user.port != ctx->id("RCLK") && user.port != ctx->id("RCLKN") &&
user.port != ctx->id("WCLK") && user.port != ctx->id("WCLKN") && user.port != ctx->id("RCLKE") &&
user.port != ctx->id("WCLKE")) {
uc->ports[user.port].net = nullptr;
} else { } else {
uc->ports[user.port].net = constnet; uc->ports[user.port].net = constnet;
constnet->users.push_back(user); constnet->users.push_back(user);
@ -421,9 +256,9 @@ static void pack_io(Context *ctx)
} }
} else { } else {
// Create a IOBUF buffer // Create a IOBUF buffer
std::unique_ptr<CellInfo> ice_cell = create_xc7_cell(ctx, ctx->id("IOBUF"), ci->name.str(ctx)); std::unique_ptr<CellInfo> xc7_cell = create_xc7_cell(ctx, ctx->id("IOBUF"), ci->name.str(ctx));
nxio_to_sb(ctx, ci, ice_cell.get()); nxio_to_sb(ctx, ci, xc7_cell.get());
new_cells.push_back(std::move(ice_cell)); new_cells.push_back(std::move(xc7_cell));
sb = new_cells.back().get(); sb = new_cells.back().get();
} }
packed_cells.insert(ci->name); packed_cells.insert(ci->name);
@ -443,8 +278,7 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
{ {
if (is_clock_port(ctx, port) || is_reset_port(ctx, port) || is_enable_port(ctx, port)) if (is_clock_port(ctx, port) || is_reset_port(ctx, port) || is_enable_port(ctx, port))
return false; return false;
return !is_sb_io(ctx, port.cell) && !is_sb_pll40(ctx, port.cell) && !is_sb_pll40_pad(ctx, port.cell) && return !is_sb_io(ctx, port.cell) && port.cell->type != id_BUFGCTRL;
port.cell->type != ctx->id("SB_GB");
} }
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)
@ -631,242 +465,230 @@ static void pack_special(Context *ctx)
for (auto cell : sorted(ctx->cells)) { for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second; CellInfo *ci = cell.second;
if (is_sb_lfosc(ctx, ci)) { if (ci->type == id_BUFGCTRL) {
std::unique_ptr<CellInfo> packed = ci->params.emplace(ctx->id("PRESELECT_I0"), "FALSE");
create_xc7_cell(ctx, ctx->id("ICESTORM_LFOSC"), ci->name.str(ctx) + "_OSC"); ci->params.emplace(ctx->id("CE0INV"), "CE0");
packed_cells.insert(ci->name); ci->params.emplace(ctx->id("S0INV"), "S0");
replace_port(ci, ctx->id("CLKLFEN"), packed.get(), ctx->id("CLKLFEN")); ci->params.emplace(ctx->id("IGNORE0INV"), "IGNORE0");
replace_port(ci, ctx->id("CLKLFPU"), packed.get(), ctx->id("CLKLFPU")); ci->params.emplace(ctx->id("CE1INV"), "CE1");
if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME ci->params.emplace(ctx->id("S1INV"), "S1");
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF_FABRIC")); ci->params.emplace(ctx->id("IGNORE1INV"), "IGNORE1");
} else { } else if (ci->type == id_MMCME2_ADV) {
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF")); ci->params.emplace(ctx->id("BANDWIDTH"), "OPTIMIZED");
} ci->params.emplace(ctx->id("CLKBURST_ENABLE"), "FALSE");
new_cells.push_back(std::move(packed)); ci->params.emplace(ctx->id("CLKBURST_REPEAT"), "FALSE");
} else if (is_sb_hfosc(ctx, ci)) { ci->params.emplace(ctx->id("CLKFBIN_EDGE"), "FALSE");
std::unique_ptr<CellInfo> packed = ci->params.emplace(ctx->id("CLKFBIN_NOCOUNT"), "TRUE");
create_xc7_cell(ctx, ctx->id("ICESTORM_HFOSC"), ci->name.str(ctx) + "_OSC"); ci->params.emplace(ctx->id("CLKFBOUT_EDGE"), "FALSE");
packed_cells.insert(ci->name); ci->params.emplace(ctx->id("CLKFBOUT_EN"), "TRUE");
packed->params[ctx->id("CLKHF_DIV")] = str_or_default(ci->params, ctx->id("CLKHF_DIV"), "0b00"); ci->params.emplace(ctx->id("CLKFBOUT_FRAC_EN"), "FALSE");
replace_port(ci, ctx->id("CLKHFEN"), packed.get(), ctx->id("CLKHFEN")); ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_FALL"), "FALSE");
replace_port(ci, ctx->id("CLKHFPU"), packed.get(), ctx->id("CLKHFPU")); ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_RISE"), "FALSE");
if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME ci->params.emplace(ctx->id("CLKFBOUT_NOCOUNT"), "TRUE");
replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF_FABRIC")); ci->params.emplace(ctx->id("CLKFBOUT_USE_FINE_PS"), "FALSE");
} else { ci->params.emplace(ctx->id("CLKINSELINV"), "CLKINSEL");
replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF")); ci->params.emplace(ctx->id("CLKOUT0_EDGE"), "FALSE");
} ci->params.emplace(ctx->id("CLKOUT0_EN"), "FALSE");
new_cells.push_back(std::move(packed)); ci->params.emplace(ctx->id("CLKOUT0_FRAC_EN"), "FALSE");
} else if (is_sb_spram(ctx, ci)) { ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_FALL"), "FALSE");
std::unique_ptr<CellInfo> packed = ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_RISE"), "FALSE");
create_xc7_cell(ctx, ctx->id("ICESTORM_SPRAM"), ci->name.str(ctx) + "_RAM"); ci->params.emplace(ctx->id("CLKOUT0_NOCOUNT"), "TRUE");
packed_cells.insert(ci->name); ci->params.emplace(ctx->id("CLKOUT0_USE_FINE_PS"), "FALSE");
for (auto port : ci->ports) { ci->params.emplace(ctx->id("CLKOUT1_EDGE"), "FALSE");
PortInfo &pi = port.second; ci->params.emplace(ctx->id("CLKOUT1_EN"), "FALSE");
std::string newname = pi.name.str(ctx); ci->params.emplace(ctx->id("CLKOUT1_NOCOUNT"), "TRUE");
size_t bpos = newname.find('['); ci->params.emplace(ctx->id("CLKOUT1_USE_FINE_PS"), "FALSE");
if (bpos != std::string::npos) { ci->params.emplace(ctx->id("CLKOUT2_EDGE"), "FALSE");
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); ci->params.emplace(ctx->id("CLKOUT2_EN"), "FALSE");
} ci->params.emplace(ctx->id("CLKOUT2_NOCOUNT"), "TRUE");
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); ci->params.emplace(ctx->id("CLKOUT2_USE_FINE_PS"), "FALSE");
} ci->params.emplace(ctx->id("CLKOUT3_EDGE"), "FALSE");
new_cells.push_back(std::move(packed)); ci->params.emplace(ctx->id("CLKOUT3_EN"), "FALSE");
} else if (is_sb_mac16(ctx, ci)) { ci->params.emplace(ctx->id("CLKOUT3_NOCOUNT"), "TRUE");
std::unique_ptr<CellInfo> packed = ci->params.emplace(ctx->id("CLKOUT3_USE_FINE_PS"), "FALSE");
create_xc7_cell(ctx, ctx->id("ICESTORM_DSP"), ci->name.str(ctx) + "_DSP"); ci->params.emplace(ctx->id("CLKOUT4_CASCADE"), "FALSE");
packed_cells.insert(ci->name); ci->params.emplace(ctx->id("CLKOUT4_EDGE"), "FALSE");
for (auto attr : ci->attrs) ci->params.emplace(ctx->id("CLKOUT4_EN"), "FALSE");
packed->attrs[attr.first] = attr.second; ci->params.emplace(ctx->id("CLKOUT4_NOCOUNT"), "TRUE");
for (auto param : ci->params) ci->params.emplace(ctx->id("CLKOUT4_USE_FINE_PS"), "FALSE");
packed->params[param.first] = param.second; ci->params.emplace(ctx->id("CLKOUT5_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT5_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT5_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT5_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT6_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT6_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT6_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT6_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("COMPENSATION"), "INTERNAL");
ci->params.emplace(ctx->id("DIRECT_PATH_CNTRL"), "FALSE");
ci->params.emplace(ctx->id("DIVCLK_EDGE"), "FALSE");
ci->params.emplace(ctx->id("DIVCLK_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("EN_VCO_DIV1"), "FALSE");
ci->params.emplace(ctx->id("EN_VCO_DIV6"), "FALSE");
ci->params.emplace(ctx->id("GTS_WAIT"), "FALSE");
ci->params.emplace(ctx->id("HVLF_CNT_TEST_EN"), "FALSE");
ci->params.emplace(ctx->id("INTERP_TEST"), "FALSE");
ci->params.emplace(ctx->id("IN_DLY_EN"), "TRUE");
ci->params.emplace(ctx->id("LF_LOW_SEL"), "FALSE");
ci->params.emplace(ctx->id("MMCM_EN"), "TRUE");
ci->params.emplace(ctx->id("PERF0_USE_CLK"), "FALSE");
ci->params.emplace(ctx->id("PERF1_USE_CLK"), "FALSE");
ci->params.emplace(ctx->id("PERF2_USE_CLK"), "FALSE");
ci->params.emplace(ctx->id("PERF3_USE_CLK"), "FALSE");
ci->params.emplace(ctx->id("PSENINV"), "PSEN");
ci->params.emplace(ctx->id("PSINCDECINV"), "PSINCDEC");
ci->params.emplace(ctx->id("PWRDWNINV"), "PWRDWN");
ci->params.emplace(ctx->id("RSTINV"), "RST");
ci->params.emplace(ctx->id("SEL_HV_NMOS"), "FALSE");
ci->params.emplace(ctx->id("SEL_LV_NMOS"), "FALSE");
ci->params.emplace(ctx->id("SEL_SLIPD"), "FALSE");
ci->params.emplace(ctx->id("SS_EN"), "FALSE");
ci->params.emplace(ctx->id("SS_MODE"), "CENTER_HIGH");
ci->params.emplace(ctx->id("STARTUP_WAIT"), "FALSE");
ci->params.emplace(ctx->id("SUP_SEL_AREG"), "FALSE");
ci->params.emplace(ctx->id("SUP_SEL_DREG"), "FALSE");
ci->params.emplace(ctx->id("TMUX_MUX_SEL"), "00");
ci->params.emplace(ctx->id("VLF_HIGH_DIS_B"), "TRUE");
ci->params.emplace(ctx->id("VLF_HIGH_PWDN_B"), "TRUE");
//ci->params.emplace(ctx->id("MMCME2_ADV:mmcm_adv_inst:");
ci->params.emplace(ctx->id("ANALOG_MISC"), "0000");
ci->params.emplace(ctx->id("AVDD_COMP_SET"), "011");
ci->params.emplace(ctx->id("AVDD_VBG_PD"), "110");
ci->params.emplace(ctx->id("AVDD_VBG_SEL"), "1001");
ci->params.emplace(ctx->id("CLKBURST_CNT"), "1");
ci->params.emplace(ctx->id("CLKFBIN_HT"), "1");
ci->params.emplace(ctx->id("CLKFBIN_LT"), "1");
ci->params.emplace(ctx->id("CLKFBIN_MULT"), "1");
ci->params.emplace(ctx->id("CLKFBOUT_DT"), "0");
ci->params.emplace(ctx->id("CLKFBOUT_FRAC"), "0");
ci->params.emplace(ctx->id("CLKFBOUT_HT"), "1");
ci->params.emplace(ctx->id("CLKFBOUT_LT"), "1");
ci->params.emplace(ctx->id("CLKFBOUT_MULT_F"), "40.5");
ci->params.emplace(ctx->id("CLKFBOUT_MX"), "00");
ci->params.emplace(ctx->id("CLKFBOUT_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKFBOUT_PM_FALL"), "000");
ci->params.emplace(ctx->id("CLKFBOUT_PM_RISE"), "000");
ci->params.emplace(ctx->id("CLKFB_MUX_SEL"), "000");
ci->params.emplace(ctx->id("CLKIN1_MUX_SEL"), "000");
ci->params.emplace(ctx->id("CLKIN1_PERIOD"), "8");
ci->params.emplace(ctx->id("CLKIN2_MUX_SEL"), "000");
ci->params.emplace(ctx->id("CLKIN2_PERIOD"), "0");
ci->params.emplace(ctx->id("CLKOUT0_DIVIDE_F"), "16.875");
ci->params.emplace(ctx->id("CLKOUT0_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT0_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT0_FRAC"), "0");
ci->params.emplace(ctx->id("CLKOUT0_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT0_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT0_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT0_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT0_PM_FALL"), "000");
ci->params.emplace(ctx->id("CLKOUT0_PM_RISE"), "000");
ci->params.emplace(ctx->id("CLKOUT1_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT1_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT1_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT1_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT1_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT1_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT1_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT1_PM"), "000");
ci->params.emplace(ctx->id("CLKOUT2_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT2_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT2_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT2_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT2_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT2_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT2_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT2_PM"), "000");
ci->params.emplace(ctx->id("CLKOUT3_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT3_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT3_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT3_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT3_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT3_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT3_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT3_PM"), "000");
ci->params.emplace(ctx->id("CLKOUT4_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT4_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT4_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT4_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT4_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT4_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT4_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT4_PM"), "000");
ci->params.emplace(ctx->id("CLKOUT5_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT5_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT5_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT5_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT5_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT5_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT5_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT5_PM"), "000");
ci->params.emplace(ctx->id("CLKOUT6_DIVIDE"), "1");
ci->params.emplace(ctx->id("CLKOUT6_DT"), "0");
ci->params.emplace(ctx->id("CLKOUT6_DUTY_CYCLE"), "0.5");
ci->params.emplace(ctx->id("CLKOUT6_HT"), "1");
ci->params.emplace(ctx->id("CLKOUT6_LT"), "1");
ci->params.emplace(ctx->id("CLKOUT6_MX"), "00");
ci->params.emplace(ctx->id("CLKOUT6_PHASE"), "0.0");
ci->params.emplace(ctx->id("CLKOUT6_PM"), "000");
ci->params.emplace(ctx->id("CONTROL_0"), "1111001101111100");
ci->params.emplace(ctx->id("CONTROL_1"), "0111110101001101");
ci->params.emplace(ctx->id("CONTROL_2"), "0101000001000010");
ci->params.emplace(ctx->id("CONTROL_3"), "1110101111001000");
ci->params.emplace(ctx->id("CONTROL_4"), "1101010011011111");
ci->params.emplace(ctx->id("CONTROL_5"), "1010110111111011");
ci->params.emplace(ctx->id("CONTROL_6"), "1011001011000011");
ci->params.emplace(ctx->id("CONTROL_7"), "0100110000101110");
ci->params.emplace(ctx->id("CP"), "0000");
ci->params.emplace(ctx->id("CP_BIAS_TRIP_SET"), "0");
ci->params.emplace(ctx->id("CP_RES"), "01");
ci->params.emplace(ctx->id("DIVCLK_DIVIDE"), "5");
ci->params.emplace(ctx->id("DIVCLK_HT"), "1");
ci->params.emplace(ctx->id("DIVCLK_LT"), "1");
ci->params.emplace(ctx->id("DVDD_COMP_SET"), "011");
ci->params.emplace(ctx->id("DVDD_VBG_PD"), "110");
ci->params.emplace(ctx->id("DVDD_VBG_SEL"), "1001");
ci->params.emplace(ctx->id("EN_CURR_SINK"), "11");
ci->params.emplace(ctx->id("FINE_PS_FRAC"), "0");
ci->params.emplace(ctx->id("FREQ_BB_USE_CLK0"), "0");
ci->params.emplace(ctx->id("FREQ_BB_USE_CLK1"), "0");
ci->params.emplace(ctx->id("FREQ_BB_USE_CLK2"), "0");
ci->params.emplace(ctx->id("FREQ_BB_USE_CLK3"), "0");
ci->params.emplace(ctx->id("FREQ_COMP"), "01");
ci->params.emplace(ctx->id("HROW_DLY_SET"), "0");
ci->params.emplace(ctx->id("HVLF_CNT_TEST"), "0");
ci->params.emplace(ctx->id("INTERP_EN"), "00010000");
ci->params.emplace(ctx->id("IN_DLY_MX_CVDD"), "011000");
ci->params.emplace(ctx->id("IN_DLY_MX_DVDD"), "000001");
ci->params.emplace(ctx->id("IN_DLY_SET"), "38");
ci->params.emplace(ctx->id("LFHF"), "11");
ci->params.emplace(ctx->id("LF_NEN"), "10");
ci->params.emplace(ctx->id("LF_PEN"), "00");
ci->params.emplace(ctx->id("LOCK_CNT"), "128");
ci->params.emplace(ctx->id("LOCK_FB_DLY"), "3");
ci->params.emplace(ctx->id("LOCK_REF_DLY"), "3");
ci->params.emplace(ctx->id("LOCK_SAT_HIGH"), "160");
ci->params.emplace(ctx->id("MAN_LF"), "000");
ci->params.emplace(ctx->id("MVDD_SEL"), "11");
ci->params.emplace(ctx->id("PERF0_MUX_SEL"), "000");
ci->params.emplace(ctx->id("PERF1_MUX_SEL"), "000");
ci->params.emplace(ctx->id("PERF2_MUX_SEL"), "000");
ci->params.emplace(ctx->id("PERF3_MUX_SEL"), "000");
ci->params.emplace(ctx->id("PFD"), "0100001");
ci->params.emplace(ctx->id("REF_JITTER1"), "0.01");
ci->params.emplace(ctx->id("REF_JITTER2"), "0.01");
ci->params.emplace(ctx->id("RES"), "0000");
ci->params.emplace(ctx->id("SKEW_FLOP_INV"), "0000");
ci->params.emplace(ctx->id("SPARE_ANALOG"), "00000");
ci->params.emplace(ctx->id("SPARE_DIGITAL"), "00000");
ci->params.emplace(ctx->id("SS_MOD_PERIOD"), "10000");
ci->params.emplace(ctx->id("SS_STEPS"), "011");
ci->params.emplace(ctx->id("SS_STEPS_INIT"), "010");
ci->params.emplace(ctx->id("SYNTH_CLK_DIV"), "11");
ci->params.emplace(ctx->id("UNLOCK_CNT"), "64");
ci->params.emplace(ctx->id("VREF_START"), "01");
for (auto port : ci->ports) { ci->params[ctx->id("COMPENSATION")] = "INTERNAL";
PortInfo &pi = port.second;
std::string newname = pi.name.str(ctx);
size_t bpos = newname.find('[');
if (bpos != std::string::npos) {
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2);
}
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_pll40(ctx, ci)) {
bool is_pad = is_sb_pll40_pad(ctx, ci);
bool is_core = !is_pad;
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL");
packed->attrs[ctx->id("TYPE")] = ci->type.str(ctx);
packed_cells.insert(ci->name);
for (auto attr : ci->attrs)
packed->attrs[attr.first] = attr.second;
for (auto param : ci->params)
packed->params[param.first] = param.second;
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;
packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
NetInfo *pad_packagepin_net = nullptr;
for (auto port : ci->ports) {
PortInfo &pi = port.second;
std::string newname = pi.name.str(ctx);
size_t bpos = newname.find('[');
if (bpos != std::string::npos) {
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2);
}
if (pi.name == ctx->id("PLLOUTCOREA"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTCOREB"))
newname = "PLLOUT_B";
if (pi.name == ctx->id("PLLOUTCORE"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PACKAGEPIN")) {
if (!is_pad) {
log_error(" PLL '%s' has a PACKAGEPIN but is not a PAD PLL", ci->name.c_str(ctx));
} else {
// We drop this port and instead place the PLL adequately below.
pad_packagepin_net = port.second.net;
NPNR_ASSERT(pad_packagepin_net != nullptr);
continue;
}
}
if (pi.name == ctx->id("REFERENCECLK")) {
if (!is_core)
log_error(" PLL '%s' has a REFERENCECLK but is not a CORE PLL", ci->name.c_str(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;
// 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));
}
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);
}
log_info(" constrained '%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;
}
if (!constrained) {
log_error(" could not constrain '%s' to any PLL Bel\n", packed->name.c_str(ctx));
}
}
// Delete the original PACKAGEPIN net if needed.
if (pad_packagepin_net != nullptr) {
for (auto user : pad_packagepin_net->users) {
user.cell->ports.erase(user.port);
}
ctx->nets.erase(pad_packagepin_net->name);
pad_packagepin_net = nullptr;
}
// The LOCK signal on iCE40 PLLs goes through the neigh_op_bnl_1 wire.
// In practice, this means the LOCK signal can only directly reach LUT
// inputs.
// If we have a net connected to LOCK, make sure it only drives LUTs.
auto port = packed->ports[ctx->id("LOCK")];
if (port.net != nullptr) {
bool found_lut = false;
bool all_luts = true;
unsigned int lut_count = 0;
for (const auto &user : port.net->users) {
NPNR_ASSERT(user.cell != nullptr);
if (user.cell->type == ctx->id("XC7_LC")) {
found_lut = true;
lut_count++;
} else {
all_luts = false;
}
}
if (found_lut && all_luts) {
// Every user is a LUT, carry on now.
} else if (found_lut && !all_luts && lut_count < 8) {
// Strategy: create a pass-through LUT, move all non-LUT users behind it.
log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx));
auto pt = spliceLUT(ctx, packed.get(), port.name, true);
new_cells.push_back(std::move(pt));
} else {
// Strategy: create a pass-through LUT, move every user behind it.
log_info(" LUT strategy for %s: move all users to new LUT\n", port.name.c_str(ctx));
auto pt = spliceLUT(ctx, packed.get(), port.name, false);
new_cells.push_back(std::move(pt));
}
// Find wire that will be driven by this port.
const auto pll_out_wire = ctx->getBelPinWire(pll_bel, port.name);
NPNR_ASSERT(pll_out_wire != WireId());
// Now, constrain all LUTs on the output of the signal to be at
// the correct Bel relative to the PLL Bel.
// int x = ctx->chip_info->wire_data[pll_out_wire.index].x;
// int y = ctx->chip_info->wire_data[pll_out_wire.index].y;
// int z = 0;
// for (const auto &user : port.net->users) {
// NPNR_ASSERT(user.cell != nullptr);
// NPNR_ASSERT(user.cell->type == ctx->id("XC7_LC"));
// // TODO(q3k): handle when the Bel might be already the
// // target of another constraint.
// NPNR_ASSERT(z < 8);
// auto target_bel = ctx->getBelByLocation(Loc(x, y, z++));
// auto target_bel_name = ctx->getBelName(target_bel).str(ctx);
// user.cell->attrs[ctx->id("BEL")] = target_bel_name;
// log_info(" constrained '%s' to %s\n", user.cell->name.c_str(ctx), target_bel_name.c_str());
//}
}
new_cells.push_back(std::move(packed));
} }
} }

View File

@ -1,3 +1,3 @@
NET "clk" PERIOD = 8 nS ; NET "pll.clkin1" PERIOD = 8 nS ;
PIN "clk_pin" = BEL "clk.PAD" PINNAME PAD; #PIN "clk_pin" = BEL "clk.PAD" PINNAME PAD;
PIN "clk_pin" CLOCK_DEDICATED_ROUTE = FALSE; #PIN "clk_pin" CLOCK_DEDICATED_ROUTE = FALSE;

View File

@ -1,5 +1,6 @@
read_verilog picorv32.v read_verilog picorv32.v
read_verilog picorv32_top.v read_verilog picorv32_top.v
read_verilog 125MHz_to_60MHz.v
#synth_xilinx -top picorv32 #synth_xilinx -top picorv32

View File

@ -12,13 +12,7 @@ module top (
input [31:0] mem_rdata input [31:0] mem_rdata
); );
wire gclk; clk_wiz_v3_6 pll(.CLK_IN1(clk), .CLK_OUT1(gclk));
BUFGCTRL clk_gb (
.I0(clk),
.CE0(1'b1),
.S0(1'b1),
.O(gclk)
);
picorv32 #( picorv32 #(
.ENABLE_COUNTERS(0), .ENABLE_COUNTERS(0),

View File

@ -25,6 +25,7 @@
#include "log.h" #include "log.h"
#include "nextpnr.h" #include "nextpnr.h"
#include "util.h" #include "util.h"
#include <boost/range/adaptor/reversed.hpp>
#include "torc/Physical.hpp" #include "torc/Physical.hpp"
using namespace torc::architecture::xilinx; using namespace torc::architecture::xilinx;
@ -32,9 +33,8 @@ using namespace torc::physical;
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
void write_xdl(const Context *ctx, std::ostream &out) DesignSharedPtr create_torc_design(const Context *ctx)
{ {
XdlExporter exporter(out);
auto designPtr = Factory::newDesignPtr("name", torc_info->ddb->getDeviceName(), ctx->args.package, "-1", ""); auto designPtr = Factory::newDesignPtr("name", torc_info->ddb->getDeviceName(), ctx->args.package, "-1", "");
std::unordered_map<int32_t, InstanceSharedPtr> site_to_instance; std::unordered_map<int32_t, InstanceSharedPtr> site_to_instance;
@ -68,7 +68,7 @@ void write_xdl(const Context *ctx, std::ostream &out)
const char *type; const char *type;
if (cell.second->type == id_SLICE_LUT6) if (cell.second->type == id_SLICE_LUT6)
type = "SLICEL"; type = "SLICEL";
else if (cell.second->type == id_IOB33 || cell.second->type == id_BUFGCTRL || cell.second->type == id_PS7) else if (cell.second->type == id_IOB33 || cell.second->type == id_IOB18 || cell.second->type == id_BUFGCTRL || cell.second->type == id_PS7 || cell.second->type == id_MMCME2_ADV)
type = cell.second->type.c_str(ctx); type = cell.second->type.c_str(ctx);
else else
log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx)); log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx));
@ -111,7 +111,7 @@ void write_xdl(const Context *ctx, std::ostream &out)
// Assume from Yosys that INIT masks of less than 32 bits are output as uint32_t // Assume from Yosys that INIT masks of less than 32 bits are output as uint32_t
if (lut_inputs.size() < 6) { if (lut_inputs.size() < 6) {
auto init_as_uint = boost::lexical_cast<uint32_t>(init); auto init_as_uint = boost::lexical_cast<uint32_t>(init);
NPNR_ASSERT(init_as_uint < (1ull << (1u << lut_inputs.size()))); NPNR_ASSERT(init_as_uint <= ((1ull << (1u << lut_inputs.size())) - 1));
if (lut_inputs.empty()) if (lut_inputs.empty())
value += init; value += init;
else { else {
@ -173,16 +173,21 @@ void write_xdl(const Context *ctx, std::ostream &out)
boost::replace_all(name, ":", "\\:"); boost::replace_all(name, ":", "\\:");
instPtr->setConfig(setting, name, "#FF"); instPtr->setConfig(setting, name, "#FF");
instPtr->setConfig(setting + "MUX", "", "O6"); instPtr->setConfig(setting + "MUX", "", "O6");
instPtr->setConfig(setting + "INIT", "", "INIT" + cell.second->params.at(ctx->id("DFF_INIT"))); instPtr->setConfig(setting + "INIT", "", cell.second->params.at(ctx->id("FFINIT")));
assert(cell.second->params.at(ctx->id("SET_NORESET")) == "0"); // TODO
instPtr->setConfig(setting + "SR", "", "SRLOW");
NPNR_ASSERT(!cell.second->lcInfo.negClk); // TODO if (cell.second->lcInfo.negClk)
instPtr->setConfig("CLKINV", "", "CLK_B");
else
instPtr->setConfig("CLKINV", "", "CLK"); instPtr->setConfig("CLKINV", "", "CLK");
if (get_net_or_empty(cell.second.get(), id_SR)) {
instPtr->setConfig(setting + "SR", "", cell.second->params.at(id_SR));
instPtr->setConfig("SYNC_ATTR", "", cell.second->params.at(ctx->id("SYNC_ATTR")));
instPtr->setConfig("SRUSEDMUX", "", "IN"); instPtr->setConfig("SRUSEDMUX", "", "IN");
}
if (get_net_or_empty(cell.second.get(), ctx->id("CE")))
instPtr->setConfig("CEUSEDMUX", "", "IN"); instPtr->setConfig("CEUSEDMUX", "", "IN");
instPtr->setConfig("SYNC_ATTR", "", "ASYNC");
} }
} else if (cell.second->type == id_IOB33) { } else if (cell.second->type == id_IOB33) {
if (get_net_or_empty(cell.second.get(), id_I)) { if (get_net_or_empty(cell.second.get(), id_I)) {
@ -195,17 +200,20 @@ void write_xdl(const Context *ctx, std::ostream &out)
instPtr->setConfig("DRIVE", "", "12"); instPtr->setConfig("DRIVE", "", "12");
instPtr->setConfig("SLEW", "", "SLOW"); instPtr->setConfig("SLEW", "", "SLOW");
} }
} else if (cell.second->type == id_BUFGCTRL) { } else if (cell.second->type == id_IOB18) {
auto it = cell.second->params.find(ctx->id("PRESELECT_I0")); if (get_net_or_empty(cell.second.get(), id_I)) {
instPtr->setConfig("PRESELECT_I0", "", it != cell.second->params.end() ? it->second : "FALSE"); instPtr->setConfig("IUSED", "", "0");
instPtr->setConfig("IBUF_LOW_PWR", "", "TRUE");
instPtr->setConfig("CE0INV", "", "CE0"); instPtr->setConfig("ISTANDARD", "", "LVCMOS18");
instPtr->setConfig("S0INV", "", "S0"); } else {
instPtr->setConfig("IGNORE0INV", "", "IGNORE0"); instPtr->setConfig("OUSED", "", "0");
instPtr->setConfig("CE1INV", "", "CE1"); instPtr->setConfig("OSTANDARD", "", "LVCMOS18");
instPtr->setConfig("S1INV", "", "S1"); instPtr->setConfig("DRIVE", "", "12");
instPtr->setConfig("IGNORE1INV", "", "IGNORE1"); instPtr->setConfig("SLEW", "", "SLOW");
} else if (cell.second->type == id_PS7) { }
} else if (cell.second->type == id_BUFGCTRL || cell.second->type == id_PS7 || cell.second->type == id_MMCME2_ADV) {
for (const auto& i : cell.second->params)
instPtr->setConfig(i.first.str(ctx), "", i.second);
} else } else
log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx)); log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx));
} }
@ -224,9 +232,12 @@ void write_xdl(const Context *ctx, std::ostream &out)
const auto lut = bel_to_lut(driver.cell->bel); const auto lut = bel_to_lut(driver.cell->bel);
pin_name[0] = lut[0]; pin_name[0] = lut[0];
} }
// e.g. Convert DDRARB[0] -> DDRARB0
pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());
auto pinPtr = Factory::newInstancePinPtr(instPtr, pin_name); auto pinPtr = Factory::newInstancePinPtr(instPtr, pin_name);
netPtr->addSource(pinPtr); netPtr->addSource(pinPtr);
if (!net.second->users.empty()) {
for (const auto &user : net.second->users) { for (const auto &user : net.second->users) {
site_index = torc_info->bel_to_site_index[user.cell->bel.index]; site_index = torc_info->bel_to_site_index[user.cell->bel.index];
instPtr = site_to_instance.at(site_index); instPtr = site_to_instance.at(site_index);
@ -237,6 +248,10 @@ void write_xdl(const Context *ctx, std::ostream &out)
const auto lut = bel_to_lut(user.cell->bel); const auto lut = bel_to_lut(user.cell->bel);
pin_name[0] = lut[0]; pin_name[0] = lut[0];
} }
else {
// e.g. Convert DDRARB[0] -> DDRARB0
pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());
}
pinPtr = Factory::newInstancePinPtr(instPtr, pin_name); pinPtr = Factory::newInstancePinPtr(instPtr, pin_name);
netPtr->addSink(pinPtr); netPtr->addSink(pinPtr);
} }
@ -255,7 +270,15 @@ void write_xdl(const Context *ctx, std::ostream &out)
netPtr->addPip(p); netPtr->addPip(p);
} }
} }
}
return designPtr;
}
void write_xdl(const Context *ctx, std::ostream &out)
{
XdlExporter exporter(out);
auto designPtr = create_torc_design(ctx);
exporter(designPtr); exporter(designPtr);
} }