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);
}
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_aquire();
void pyinterpreter_release();
std::string pyinterpreter_execute_file(const char *python_file, int *errorCode);
#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_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127")
else()
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g")
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe")
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
endif()
set(CMAKE_DEFIN)

View File

@ -49,8 +49,9 @@ Getting started
### nextpnr-ice40
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).
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/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:
```

View File

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

View File

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

View File

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

View File

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

View File

@ -37,6 +37,8 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
if (driver_gb)
return 0;
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 &&
ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
delay_t negative_slack = 0;

View File

@ -512,6 +512,21 @@ struct Router1
WireId next_wire = ctx->getPipDstWire(pip);
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();
NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr;
@ -618,7 +633,11 @@ struct Router1
next_qw.penalty = next_penalty;
next_qw.bonus = next_bonus;
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);
#endif
delay_t this_est = next_qw.delay + next_qw.togo;
if (this_est / 2 - cfg.estimatePrecision > best_est)
continue;

View File

@ -9,7 +9,7 @@ bool check_all_nets_driven(Context *ctx)
{
const bool debug = false;
log_info("Rule checker, Verifying pre-placed design\n");
log_info("Rule checker, verifying imported design\n");
for (auto &cell_entry : ctx->cells) {
CellInfo *cell = cell_entry.second.get();

View File

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

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,
bool warn_on_failure = false);
// Data for the timing optimisation algorithm
struct NetCriticalityInfo
{
// One each per user
std::vector<delay_t> slack;
std::vector<float> criticality;
unsigned max_path_length = 0;
delay_t cd_worst_slack = std::numeric_limits<delay_t>::max();
};
typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
void get_criticalities(Context *ctx, NetCriticalityMap *net_crit);
NEXTPNR_NAMESPACE_END
#endif

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;
} else if (cell->type == id_DP16KD) {
return false;
} else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) {
return false;
} else {
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 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 {
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
} else {
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
cell->name.c_str(this));
}
}
@ -743,6 +756,14 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
info.setup = getDelayFromNS(1);
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;
}

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")))
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")) {
// Nothing to do
} 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));
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) {
TileGroup tg;
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->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")) {
new_cell->params[ctx->id("MODE")] = "LOGIC";
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")) {
new_cell->params[ctx->id("DIR")] = "INPUT";
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(), "I", 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(), "IOLDO", PORT_IN);
add_port(ctx, new_cell.get(), "IOLTO", PORT_IN);
} else if (type == ctx->id("LUT4")) {
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(), "CLKO", PORT_OUT);
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 {
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->id("Y"));
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
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"));

View File

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

View File

@ -299,7 +299,16 @@ class Ecp5Packer
// iobuf
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));
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) {
ctx->nets.erase(net->name);
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:
void pack()
{
pack_io();
pack_iologic();
pack_ebr();
pack_dsps();
pack_dcus();
preplace_plls();
pack_constants();
pack_dram();
pack_carries();

View File

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

View File

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

View File

@ -189,6 +189,12 @@ void BaseMainWindow::createMenusAndBars()
actionRoute->setEnabled(false);
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
actionPlay = new QAction("Play", this);
actionPlay->setIcon(QIcon(":/icons/resources/control_play.png"));
@ -249,6 +255,8 @@ void BaseMainWindow::createMenusAndBars()
menuDesign->addAction(actionAssignBudget);
menuDesign->addAction(actionPlace);
menuDesign->addAction(actionRoute);
menuDesign->addSeparator();
menuDesign->addAction(actionExecutePy);
// Add Help menu actions
menuHelp->addAction(actionAbout);
@ -268,6 +276,7 @@ void BaseMainWindow::createMenusAndBars()
mainActionBar->addAction(actionAssignBudget);
mainActionBar->addAction(actionPlace);
mainActionBar->addAction(actionRoute);
mainActionBar->addAction(actionExecutePy);
// Add worker control toolbar
QToolBar *workerControlToolBar = new QToolBar("Worker");
@ -412,6 +421,7 @@ void BaseMainWindow::disableActions()
actionAssignBudget->setEnabled(false);
actionPlace->setEnabled(false);
actionRoute->setEnabled(false);
actionExecutePy->setEnabled(true);
actionPlay->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::save_proj()
{

View File

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

View File

@ -76,4 +76,21 @@ void PythonConsole::moveCursorToEnd()
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

View File

@ -43,6 +43,7 @@ class PythonConsole : public QTextEdit, public ParseListener
void displayString(QString text);
void moveCursorToEnd();
virtual void parseEvent(const ParseMessage &message);
void execute_python(std::string filename);
protected:
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::execute_python(std::string filename) { console->execute_python(filename); }
NEXTPNR_NAMESPACE_END

View File

@ -45,6 +45,7 @@ class PythonTab : public QWidget
void newContext(Context *ctx);
void info(std::string str);
void clearBuffer();
void execute_python(std::string filename);
private:
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()
{
log_write_function = nullptr;
if (workerThread.isRunning())
terminate_thread();
workerThread.quit();

View File

@ -26,6 +26,7 @@
#include "nextpnr.h"
#include "placer1.h"
#include "router1.h"
#include "timing_opt.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
@ -626,7 +627,18 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
// -----------------------------------------------------------------------
bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); }
bool Arch::place()
{
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
if (bool_or_default(settings, id("opt_timing"), false)) {
TimingOptCfg tocfg(getCtx());
tocfg.cellTypes.insert(id_ICESTORM_LC);
return timing_opt(getCtx(), tocfg);
} else {
return true;
}
}
bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
@ -950,8 +962,12 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
return TMG_IGNORE;
return TMG_ENDPOINT;
} else if (cell->type == id_SB_LEDDA_IP) {
if (port == id_CLK || port == id_CLOCK)
return TMG_CLOCK_INPUT;
return TMG_IGNORE;
}
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this));
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const

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
// prefixes other than 0b should be supported, only 0b features in docs
std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0");
assert(raw.substr(0, 2) == "0b");
if (raw.substr(0, 2) != "0b")
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
p.first.c_str(), cell->name.c_str(ctx));
raw = raw.substr(2);
value.resize(raw.length());
for (int i = 0; i < (int)raw.length(); i++) {
@ -168,7 +170,13 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
}
}
} else {
int ival = get_param_or_def(cell, ctx->id(p.first), 0);
int ival;
try {
ival = get_param_or_def(cell, ctx->id(p.first), 0);
} catch (std::invalid_argument &e) {
log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(),
cell->name.c_str(ctx));
}
for (int i = 0; i < p.second; i++)
value.push_back((ival >> i) & 0x1);
@ -486,7 +494,7 @@ void write_asc(const Context *ctx, std::ostream &out)
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP"));
bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT";
bool lvds = cell.second->ioInfo.lvds;
bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0;
bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0;
@ -495,64 +503,77 @@ void write_asc(const Context *ctx, std::ostream &out)
set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val);
}
set_config(ti, config.at(y).at(x), "NegClk", neg_trigger);
auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
NPNR_ASSERT(iez != -1);
bool input_en;
if (lvds) {
input_en = false;
pullup = false;
} else {
if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
(ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
input_en = true;
} else {
input_en = false;
}
bool input_en = false;
if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
(ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
input_en = true;
}
input_en = (input_en & !used_by_pll_out) | used_by_pll_pad;
input_en |= cell.second->ioInfo.global;
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
} else {
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
}
if (ctx->args.type == ArchArgs::UP5K) {
if (iez == 0) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
} else if (iez == 1) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
}
}
if (lvds) {
NPNR_ASSERT(z == 0);
set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true);
// Set comp IO config
auto comp_ieren = get_ieren(bi, x, y, 1);
int ciex, ciey, ciez;
std::tie(ciex, ciey, ciez) = comp_ieren;
if (!lvds) {
auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
NPNR_ASSERT(iez != -1);
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en);
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
} else {
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en);
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en);
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
}
if (ctx->args.type == ArchArgs::UP5K) {
if (ciez == 0) {
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup);
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(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup);
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 (iez == 0) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
} else if (iez == 1) {
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
}
}
}
}
@ -591,7 +612,8 @@ void write_asc(const Context *ctx, std::ostream &out)
{"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}};
configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig."));
set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig.");
} else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) {
} else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC") ||
cell.second->type == ctx->id("SB_LEDDA_IP")) {
// No config needed
} else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
@ -688,7 +710,7 @@ void write_asc(const Context *ctx, std::ostream &out)
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
if (iez != -1) {
// IO is not actually unused if part of an LVDS pair
// If IO is in LVDS pair, it will be configured by the other pair
if (z == 1) {
BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0});
const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0);

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(), "RGB1", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
} else if (type == ctx->id("SB_LEDDA_IP")) {
add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN);
add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN);
for (int i = 0; i < 8; i++)
add_port(ctx, new_cell.get(), "LEDDDAT" + std::to_string(i), PORT_IN);
for (int i = 0; i < 3; i++)
add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN);
add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN);
add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN);
add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); // doesn't actually exist, for icecube code compatibility
// only
add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT);
add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT);
add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT);
add_port(ctx, new_cell.get(), "LEDDON", PORT_OUT);
} else {
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
}

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_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); }
inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||

View File

@ -62,7 +62,7 @@ class ChainConstrainer
bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) ||
(int(chains.back().cells.size()) > max_length);
if (split_chain) {
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
CellInfo *passout = make_carry_pass_out((*(curr_cell - 1))->ports.at(ctx->id("COUT")));
tile.pop_back();
chains.back().cells.back() = passout;
start_of_chain = true;
@ -74,10 +74,10 @@ class ChainConstrainer
(net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) !=
net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) ||
(at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) {
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")),
at_end ? nullptr : *(curr_cell + 1));
chains.back().cells.push_back(passout);
tile.push_back(passout);
start_of_chain = true;
}
}
++curr_cell;
@ -87,30 +87,75 @@ class ChainConstrainer
}
// Insert a logic cell to legalise a COUT->fabric connection
CellInfo *make_carry_pass_out(PortInfo &cout_port)
CellInfo *make_carry_pass_out(PortInfo &cout_port, CellInfo *cin_cell = nullptr)
{
NPNR_ASSERT(cout_port.net != nullptr);
std::unique_ptr<CellInfo> lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC"));
lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3
lc->params[ctx->id("CARRY_ENABLE")] = "1";
lc->ports.at(ctx->id("O")).net = cout_port.net;
lc->ports.at(id_O).net = cout_port.net;
std::unique_ptr<NetInfo> co_i3_net(new NetInfo());
co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3");
co_i3_net->driver = cout_port.net->driver;
PortRef i3_r;
i3_r.port = ctx->id("I3");
i3_r.port = id_I3;
i3_r.cell = lc.get();
co_i3_net->users.push_back(i3_r);
PortRef o_r;
o_r.port = ctx->id("O");
o_r.port = id_O;
o_r.cell = lc.get();
cout_port.net->driver = o_r;
lc->ports.at(ctx->id("I3")).net = co_i3_net.get();
lc->ports.at(id_I3).net = co_i3_net.get();
cout_port.net = co_i3_net.get();
IdString co_i3_name = co_i3_net->name;
NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end());
ctx->nets[co_i3_name] = std::move(co_i3_net);
// If COUT also connects to a CIN; preserve the carry chain
if (cin_cell) {
std::unique_ptr<NetInfo> co_cin_net(new NetInfo());
co_cin_net->name = ctx->id(lc->name.str(ctx) + "$COUT");
// Connect I1 to 1 to preserve carry chain
NetInfo *vcc = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
lc->ports.at(id_I1).net = vcc;
PortRef i1_r;
i1_r.port = id_I1;
i1_r.cell = lc.get();
vcc->users.push_back(i1_r);
// Connect co_cin_net to the COUT of the LC
PortRef co_r;
co_r.port = id_COUT;
co_r.cell = lc.get();
co_cin_net->driver = co_r;
lc->ports.at(id_COUT).net = co_cin_net.get();
// Find the user corresponding to the next CIN
int replaced_ports = 0;
if (ctx->debug)
log_info("cell: %s\n", cin_cell->name.c_str(ctx));
for (auto port : {id_CIN, id_I3}) {
auto &usr = lc->ports.at(id_O).net->users;
if (ctx->debug)
for (auto user : usr)
log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));
auto fnd_user = std::find_if(usr.begin(), usr.end(),
[&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; });
if (fnd_user != usr.end()) {
co_cin_net->users.push_back(*fnd_user);
usr.erase(fnd_user);
cin_cell->ports.at(port).net = co_cin_net.get();
++replaced_ports;
}
}
NPNR_ASSERT(replaced_ports > 0);
IdString co_cin_name = co_cin_net->name;
NPNR_ASSERT(ctx->nets.find(co_cin_name) == ctx->nets.end());
ctx->nets[co_cin_name] = std::move(co_cin_net);
}
IdString name = lc->name;
ctx->assignCellInfo(lc.get());
ctx->cells[lc->name] = std::move(lc);
@ -163,29 +208,31 @@ class ChainConstrainer
void process_carries()
{
std::vector<CellChain> carry_chains =
find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
[](const Context *ctx, const
std::vector<CellChain> carry_chains = find_chains(
ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
[](const Context *ctx, const
CellInfo *cell) {
CellInfo *carry_prev =
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
if (carry_prev != nullptr)
return carry_prev;
/*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc,
ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/
return (CellInfo *)nullptr;
},
[](const Context *ctx, const CellInfo *cell) {
CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc,
ctx->id("CIN"), false);
if (carry_next != nullptr)
return carry_next;
/*CellInfo *i3_next =
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"),
false); if (i3_next != nullptr) return i3_next;*/
return (CellInfo *)nullptr;
});
CellInfo *cell) {
CellInfo *carry_prev =
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
if (carry_prev != nullptr)
return carry_prev;
CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT"));
if (i3_prev != nullptr)
return i3_prev;
return (CellInfo *)nullptr;
},
[](const Context *ctx, const CellInfo *cell) {
CellInfo *carry_next =
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false);
if (carry_next != nullptr)
return carry_next;
CellInfo *i3_next =
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false);
if (i3_next != nullptr)
return i3_next;
return (CellInfo *)nullptr;
});
std::unordered_set<IdString> chained;
for (auto &base_chain : carry_chains) {
for (auto c : base_chain.cells)

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()("asc", po::value<std::string>(), "asc bitstream file to write");
specific.add_options()("read", po::value<std::string>(), "asc bitstream file to read");
specific.add_options()("promote-logic",
"enable promotion of 'logic' globals (in addition to clk/ce/sr by default)");
specific.add_options()("no-promote-globals", "disable all global promotion");
specific.add_options()("opt-timing", "run post-placement timing optimisation pass (experimental)");
specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
specific.add_options()("pcf-allow-unconstrained", "don't require PCF to constrain all IO");
return specific;
}
void Ice40CommandHandler::validate()
@ -83,6 +89,8 @@ void Ice40CommandHandler::customAfterLoad(Context *ctx)
std::ifstream pcf(filename);
if (!apply_pcf(ctx, filename, pcf))
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)
@ -152,7 +160,18 @@ std::unique_ptr<Context> Ice40CommandHandler::createContext()
if (vm.count("package"))
chipArgs.package = vm["package"].as<std::string>();
return std::unique_ptr<Context>(new Context(chipArgs));
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
if (vm.count("promote-logic"))
ctx->settings[ctx->id("promote_logic")] = "1";
if (vm.count("no-promote-globals"))
ctx->settings[ctx->id("no_promote_globals")] = "1";
if (vm.count("opt-timing"))
ctx->settings[ctx->id("opt_timing")] = "1";
if (vm.count("pcf-allow-unconstrained"))
ctx->settings[ctx->id("pcf_allow_unconstrained")] = "1";
return ctx;
}
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);
}
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic, int fanout)
{
log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "",
is_logic ? " [logic]" : "");
log_info("promoting %s%s%s%s (fanout %d)\n", net->name.c_str(ctx), is_reset ? " [reset]" : "",
is_cen ? " [cen]" : "", is_logic ? " [logic]" : "", fanout);
std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
@ -565,7 +565,8 @@ static void promote_globals(Context *ctx)
{
log_info("Promoting globals..\n");
const int logic_fanout_thresh = 15;
const int enable_fanout_thresh = 5;
const int enable_fanout_thresh = 15;
const int reset_fanout_thresh = 15;
std::map<IdString, int> clock_count, reset_count, cen_count, logic_count;
for (auto net : sorted(ctx->nets)) {
NetInfo *ni = net.second;
@ -637,18 +638,20 @@ static void promote_globals(Context *ctx)
});
if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh &&
(global_logic->second > global_cen->second || prom_cens >= cens_available) &&
(global_logic->second > global_reset->second || prom_resets >= resets_available)) {
(global_logic->second > global_reset->second || prom_resets >= resets_available) &&
bool_or_default(ctx->settings, ctx->id("promote_logic"), false)) {
NetInfo *logicnet = ctx->nets[global_logic->first].get();
insert_global(ctx, logicnet, false, false, true);
insert_global(ctx, logicnet, false, false, true, global_logic->second);
++prom_globals;
++prom_logics;
clock_count.erase(logicnet->name);
reset_count.erase(logicnet->name);
cen_count.erase(logicnet->name);
logic_count.erase(logicnet->name);
} else if (global_reset->second > global_clock->second && prom_resets < resets_available) {
} else if (global_reset->second > global_clock->second && prom_resets < resets_available &&
global_reset->second > reset_fanout_thresh) {
NetInfo *rstnet = ctx->nets[global_reset->first].get();
insert_global(ctx, rstnet, true, false, false);
insert_global(ctx, rstnet, true, false, false, global_reset->second);
++prom_globals;
++prom_resets;
clock_count.erase(rstnet->name);
@ -658,7 +661,7 @@ static void promote_globals(Context *ctx)
} else if (global_cen->second > global_clock->second && prom_cens < cens_available &&
global_cen->second > enable_fanout_thresh) {
NetInfo *cennet = ctx->nets[global_cen->first].get();
insert_global(ctx, cennet, false, true, false);
insert_global(ctx, cennet, false, true, false, global_cen->second);
++prom_globals;
++prom_cens;
clock_count.erase(cennet->name);
@ -667,7 +670,7 @@ static void promote_globals(Context *ctx)
logic_count.erase(cennet->name);
} else if (global_clock->second != 0) {
NetInfo *clknet = ctx->nets[global_clock->first].get();
insert_global(ctx, clknet, false, false, false);
insert_global(ctx, clknet, false, false, false, global_clock->second);
++prom_globals;
clock_count.erase(clknet->name);
reset_count.erase(clknet->name);
@ -679,6 +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
// and either all users or only non_LUT users.
static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs)
@ -844,6 +1043,10 @@ static void pack_special(Context *ctx)
ci->ports.erase(ctx->id("RGB0"));
ci->ports.erase(ctx->id("RGB1"));
ci->ports.erase(ctx->id("RGB2"));
} else if (is_sb_ledda_ip(ctx, ci)) {
/* Force placement (no choices anyway) */
cell_place_unique(ctx, ci);
} else if (is_sb_pll40(ctx, ci)) {
bool is_pad = is_sb_pll40_pad(ctx, ci);
bool is_core = !is_pad;
@ -877,13 +1080,17 @@ static void pack_special(Context *ctx)
}
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
packed->params[ctx->id("FEEDBACK_PATH")] =
feedback_path == "DELAY"
? "0"
: feedback_path == "SIMPLE" ? "1"
: feedback_path == "PHASE_AND_DELAY"
? "2"
: feedback_path == "EXTERNAL" ? "6" : feedback_path;
std::string fbp_value = feedback_path == "DELAY"
? "0"
: feedback_path == "SIMPLE"
? "1"
: feedback_path == "PHASE_AND_DELAY"
? "2"
: feedback_path == "EXTERNAL" ? "6" : feedback_path;
if (!std::all_of(fbp_value.begin(), fbp_value.end(), isdigit))
log_error("PLL '%s' has unsupported FEEDBACK_PATH value '%s'\n", ci->name.c_str(ctx),
feedback_path.c_str());
packed->params[ctx->id("FEEDBACK_PATH")] = fbp_value;
packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
NetInfo *pad_packagepin_net = nullptr;
@ -939,82 +1146,25 @@ static void pack_special(Context *ctx)
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
// If PLL is not constrained already, do that - we need this
// information to then constrain the LOCK LUT.
BelId pll_bel;
bool constrained = false;
if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) {
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_ICESTORM_PLL)
continue;
if (ctx->isBelLocked(bel))
continue;
// PLL must have been placed already in place_plls()
BelId pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
NPNR_ASSERT(pll_bel != BelId());
// A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared
// with PLLOUT_A.
if (is_pad) {
auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A);
NPNR_ASSERT(pad_packagepin_net != nullptr);
auto pll_packagepin_driver = pad_packagepin_net->driver;
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr);
if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) {
log_error("PLL '%s' has a PACKAGEPIN driven by "
"an %s, should be directly connected to an input SB_IO\n",
ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx));
}
// Deal with PAD PLL peculiarities
if (is_pad) {
NPNR_ASSERT(pad_packagepin_net != nullptr);
auto pll_packagepin_driver = pad_packagepin_net->driver;
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr);
auto packagepin_cell = pll_packagepin_driver.cell;
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL"));
auto packagepin_cell = pll_packagepin_driver.cell;
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL"));
if (packagepin_bel_name == packagepin_cell->attrs.end()) {
log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx),
packagepin_cell->name.c_str(ctx));
}
auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second));
if (pll_sb_io_belpin.bel != packagepin_bel) {
log_error("PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n",
ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(),
ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str());
}
if (pad_packagepin_net->users.size() != 1) {
log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx),
pad_packagepin_net->name.c_str(ctx));
}
// Set an attribute about this PLL's PAD SB_IO.
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second;
// Remove the connection from the SB_IO to the PLL.
packagepin_cell->ports.erase(pll_packagepin_driver.port);
}
// Set an attribute about this PLL's PAD SB_IO.
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second;
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx),
ctx->getBelName(bel).c_str(ctx));
packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
pll_bel = bel;
constrained = true;
break;
}
if (!constrained) {
log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n",
packed->name.c_str(ctx));
}
} else {
pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL)
log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n",
packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx));
if (ctx->isBelLocked(pll_bel))
log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx),
ctx->getBelName(pll_bel).c_str(ctx));
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx),
ctx->getBelName(pll_bel).c_str(ctx));
}
// Delete the original PACKAGEPIN net if needed.
if (pad_packagepin_net != nullptr) {
for (auto user : pad_packagepin_net->users) {
// Disconnect PACKAGEPIN (it's a physical HW link)
for (auto user : pad_packagepin_net->users)
user.cell->ports.erase(user.port);
}
if (pad_packagepin_net->driver.cell != nullptr)
pad_packagepin_net->driver.cell->ports.erase(pad_packagepin_net->driver.port);
packagepin_cell->ports.erase(pll_packagepin_driver.port);
ctx->nets.erase(pad_packagepin_net->name);
pad_packagepin_net = nullptr;
}
@ -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));
bool found_lut = false;
bool all_luts = true;
bool found_carry = false;
unsigned int lut_count = 0;
for (const auto &user : port.net->users) {
NPNR_ASSERT(user.cell != nullptr);
if (user.cell->type == ctx->id("ICESTORM_LC")) {
found_lut = true;
lut_count++;
if (bool_or_default(user.cell->params, ctx->id("CARRY_ENABLE"), false)) {
found_carry = true;
all_luts = false;
} else {
found_lut = true;
lut_count++;
}
} else {
all_luts = false;
}
}
if (found_lut && all_luts) {
if (found_lut && all_luts && lut_count < 8) {
// 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.
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);
@ -1118,8 +1274,10 @@ bool Arch::pack()
pack_nonlut_ffs(ctx);
pack_carries(ctx);
pack_ram(ctx);
place_plls(ctx);
pack_special(ctx);
promote_globals(ctx);
if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false))
promote_globals(ctx);
ctx->assignArchInfo();
constrain_chains(ctx);
ctx->assignArchInfo();

View File

@ -21,6 +21,7 @@
#include "pcf.h"
#include <sstream>
#include "log.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
@ -47,17 +48,43 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
if (words.size() == 0)
continue;
std::string cmd = words.at(0);
bool nowarn = false;
if (cmd == "set_io") {
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++;
}
if (args_end >= words.size() - 1)
log_error("expected PCF syntax 'set_io cell pin' (on line %d)\n", lineno);
std::string cell = words.at(args_end);
std::string pin = words.at(args_end + 1);
auto fnd_cell = ctx->cells.find(ctx->id(cell));
if (fnd_cell == ctx->cells.end()) {
log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno);
if (!nowarn)
log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno);
} else {
BelId pin_bel = ctx->getPackagePinBel(pin);
if (pin_bel == BelId())
@ -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);
log_info("constrained '%s' to bel '%s'\n", cell.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 {
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);
return true;
} 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)
if path.exists(ascfile):
os.remove(ascfile)
result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "40", "--opt-timing"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
if result.returncode != 0:
print("Run {} failed!".format(run))
else:

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) {
net->users.push_back(pr);
} else if (type == PORT_OUT) {
assert(net->driver.cell == nullptr);
if (net->driver.cell != nullptr)
log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n",
net->name.c_str(ctx), net->driver.cell->name.c_str(ctx),
net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx),
pr.port.c_str(ctx));
net->driver = pr;
}
}
@ -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};
// Special case: input, etc, directly drives inout
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;
}
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());
// 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
std::vector<IdString> netnames;
std::vector<std::string> netlabels;
if (node->data_dict.count("netnames")) {
JsonNode *cell_parent = node->data_dict.at("netnames");
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();
for (size_t i = 0; i < num_bits; i++) {
int netid = bits->data_array.at(i)->data_number;
if (netid >= int(netnames.size()))
netnames.resize(netid + 1);
netnames.at(netid) = ctx->id(
basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]")));
if (netid >= int(netlabels.size()))
netlabels.resize(netid + 1);
std::string name =
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")) {
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++) {
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;
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);

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 "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"
//#define TORC_INFO_DB "torc_info.ar"
NEXTPNR_NAMESPACE_BEGIN
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+)");
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_loc.reserve(sites.getSiteCount() * 4);
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 &pd = site.getPrimitiveDefPtr();
const auto &type = pd->getName();
const auto &tileInfo = tiles.getTileInfo(site.getTileIndex());
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));
int x, y;
std::tie(x,y) = tile_to_xy[site.getTileIndex()];
if (type == "SLICEL" || type == "SLICEM") {
bel_to_site_index.push_back(i);
@ -79,11 +67,10 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
bel_to_site_index.push_back(i);
bel_to_site_index.push_back(i);
site_index_to_type[i] = id_SLICE_LUT6;
const auto site_name = site.getName();
const auto site_name = site.getName();
if (!boost::regex_match(site_name.c_str(), what, re_loc))
throw;
const auto sx = boost::lexical_cast<int>(what.str(1));
const auto site_name_back = site_name.back();
if ((sx & 1) == 0) {
bel_to_loc.emplace_back(x, y, 0);
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") {
bel_to_site_index.push_back(i);
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);
site_index_to_bel[i] = b;
++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_FAN("FAN(_ALT)?\\d");
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_o("(CMT|CLK)_BUFG_BUFGCTRL\\d+_O");
const boost::regex int_clk("CLK(_L)?[01]");
const boost::regex gclk("GCLK_(L_)?B\\d+(_EAST|_WEST)?");
const boost::regex bufg_i("CLK_BUFG_BUFGCTRL\\d+_I0");
const boost::regex bufg_o("CLK_BUFG_BUFGCTRL\\d+_O");
const boost::regex hrow("CLK_HROW_CLK[01]_[34]");
std::unordered_map</*TileTypeIndex*/ unsigned, std::vector<delay_t>> delay_lookup;
std::unordered_map<Segments::SegmentReference, TileIndex> segment_to_anchor;
Tilewire currentTilewire;
WireId w;
w.index = 0;
@ -141,8 +136,28 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const auto &currentSegment = segments.getTilewireSegment(currentTilewire);
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;
segment_to_wire.emplace(currentSegment, w);
} else
trivial_to_wire.emplace(currentTilewire, w);
@ -224,9 +239,11 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
++w.index;
}
}
segment_to_anchor.clear();
wire_to_tilewire.shrink_to_fit();
wire_to_delay.shrink_to_fit();
num_wires = wire_to_tilewire.size();
wire_is_global.resize(num_wires);
wire_to_pips_downhill.resize(num_wires);
// std::unordered_map<Arc, int> arc_to_pip;
@ -238,60 +255,77 @@ TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::str
const auto &currentTilewire = wire_to_tilewire[w.index];
if (currentTilewire.isUndefined())
continue;
arcs.clear();
const auto &tileInfo = tiles.getTileInfo(currentTilewire.getTileIndex());
const auto tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
const bool clb = boost::starts_with(
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];
pips.reserve(arcs.size());
const bool clk_tile = boost::starts_with(tileTypeName, "CMT") || boost::starts_with(tileTypeName, "CLK");
const bool int_tile = boost::starts_with(tileTypeName, "INT");
const bool clk_tile = boost::starts_with(tileTypeName, "CLK");
for (const auto &a : arcs) {
// Disable BUFG I0 -> O routethrough
if (clk_tile) {
ewi.set(a.getSourceTilewire());
if (boost::regex_match(ewi.mWireName, bufg_i)) {
ewi.set(a.getSinkTilewire());
if (boost::regex_match(ewi.mWireName, bufg_o))
continue;
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++);
// Disable BUFG I0 -> O routethrough
if (clk_tile) {
ewi.set(a.getSourceTilewire());
if (boost::regex_match(ewi.mWireName, bufg_i)) {
ewi.set(a.getSinkTilewire());
if (boost::regex_match(ewi.mWireName, bufg_o))
continue;
}
}
// Disable entering HROW from INT_[LR].CLK[01]
if (boost::starts_with(tileTypeName, "CLK_HROW")) {
ewi.set(a.getSourceTilewire());
if (boost::regex_match(ewi.mWireName, hrow))
continue;
}
pips.emplace_back(p);
pip_to_arc.emplace_back(a);
// arc_to_pip.emplace(a, p.index);
++p.index;
}
}
// Disable CLK inputs from being driven from the fabric (must be from global clock network)
else if (int_tile) {
ewi.set(a.getSinkTilewire());
if (boost::regex_match(ewi.mWireName, int_clk)) {
ewi.set(a.getSourceTilewire());
if (!boost::regex_match(ewi.mWireName, gclk))
continue;
}
}
pips.emplace_back(p);
pip_to_arc.emplace_back(a);
// arc_to_pip.emplace(a, p.index);
const auto &tw = a.getSinkTilewire();
pip_to_dst_wire.emplace_back(tilewire_to_wire(tw));
++p.index;
}
pips.shrink_to_fit();
if (global_tile)
wire_is_global[w.index] = true;
}
pip_to_arc.shrink_to_fit();
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();
width = (int)tiles.getColCount();
}
@ -316,29 +350,9 @@ Arch::Arch(ArchArgs args) : args(args)
{
torc::common::DirectoryTree directoryTree("/opt/torc/src/torc");
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));
#ifdef TORC_INFO_DB
std::ofstream ofs(TORC_INFO_DB, std::ios::binary);
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
}
torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7z020", args.package));
} else if (args.type == ArchArgs::VX980) {
torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7vx980t", args.package));
} else {
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);
}
// 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);
wire_to_net.resize(torc_info->num_wires);
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) {
return "z020";
} else if (args.type == ArchArgs::VX980) {
return "vx980";
} else {
log_error("Unsupported XC7 chip type.\n");
}
@ -385,6 +389,8 @@ IdString Arch::archArgsToId(ArchArgs args) const
{
if (args.type == ArchArgs::Z020)
return id("z020");
if (args.type == ArchArgs::VX980)
return id("vx980");
return IdString();
}
@ -510,10 +516,9 @@ WireId Arch::getBelPinWire(BelId bel, IdString pin) const
throw;
}
}
} else if (bel_type == id_PS7) {
} else if (bel_type == id_PS7 || bel_type == id_MMCME2_ADV) {
// e.g. Convert DDRARB[0] -> DDRARB0
boost::erase_all(pin_name, "[");
boost::erase_all(pin_name, "]");
pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());
}
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());
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> ret;
/* 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));
*/
NPNR_ASSERT("TODO");
return ret;
}
@ -576,7 +544,6 @@ std::vector<IdString> Arch::getBelPins(BelId bel) const
WireId Arch::getWireByName(IdString name) const
{
WireId ret;
if (wire_by_name.empty()) {
for (int i = 0; i < torc_info->num_wires; 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
{
NPNR_ASSERT(wire != WireId());
// switch (chip_info->wire_data[wire.index].type) {
// 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");
// }
NPNR_ASSERT("TODO");
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>> ret;
NPNR_ASSERT("TODO");
return ret;
}
@ -664,27 +601,12 @@ IdString Arch::getPipName(PipId pip) const
std::stringstream pip_name;
pip_name << ewi_src.mTileName << "." << ewi_src.mWireName << ".->." << ewi_dst.mWireName;
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>> ret;
NPNR_ASSERT("TODO");
return ret;
}
@ -694,11 +616,7 @@ BelId Arch::getPackagePinBel(const std::string &pin) const { return getBelByName
std::string Arch::getBelPackagePin(BelId bel) const
{
// for (int i = 0; i < package_info->num_pins; i++) {
// if (package_info->pins[i].bel_index == bel.index) {
// return std::string(package_info->pins[i].name.get());
// }
// }
NPNR_ASSERT("TODO");
return "";
}
@ -717,39 +635,7 @@ IdString Arch::getGroupName(GroupId group) const
std::string suffix;
switch (group.type) {
case GroupId::TYPE_FRAME:
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;
NPNR_ASSERT("TODO");
default:
return IdString();
}
@ -760,52 +646,7 @@ IdString Arch::getGroupName(GroupId group) const
std::vector<GroupId> Arch::getGroups() const
{
std::vector<GroupId> ret;
/*
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);
}
}
}*/
NPNR_ASSERT("TODO");
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> ret;
NPNR_ASSERT("TODO");
return ret;
}
std::vector<GroupId> Arch::getGroupGroups(GroupId group) const
{
std::vector<GroupId> ret;
NPNR_ASSERT("TODO");
return ret;
}
@ -965,7 +808,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
}
// TODO
// if (port == id_OMUX)
} else if (cell->type == id_IOB33) {
} else if (cell->type == id_IOB33 || cell->type == id_IOB18) {
if (port == id_I)
return TMG_STARTPOINT;
else if (port == id_O)
@ -977,6 +820,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
} else if (cell->type == id_PS7) {
// TODO
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));
}
@ -1056,109 +901,3 @@ void Arch::assignCellInfo(CellInfo *cell)
}
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.
#endif
#include <boost/serialization/access.hpp>
#include "torc/Architecture.hpp"
#include "torc/Common.hpp"
using namespace torc::architecture;
@ -332,33 +330,12 @@ struct TorcInfo
std::vector<std::vector<PipId>> wire_to_pips_downhill;
std::vector<Arc> pip_to_arc;
int num_pips;
std::vector<WireId> pip_to_dst_wire;
int width;
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);
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;
@ -500,7 +477,8 @@ struct ArchArgs
enum ArchArgsTypes
{
NONE,
Z020
Z020,
VX980
} type = NONE;
std::string package;
};
@ -683,7 +661,6 @@ struct Arch : BaseCtx
auto pip = it->second.pip;
if (pip != PipId()) {
pip_to_net[pip.index] = nullptr;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr;
}
net_wires.erase(it);
@ -716,10 +693,7 @@ struct Arch : BaseCtx
BelPinRange getWireBelPins(WireId wire) const
{
BelPinRange range;
// NPNR_ASSERT(wire != WireId());
// 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;
// TODO
return range;
}
@ -739,10 +713,8 @@ struct Arch : BaseCtx
{
NPNR_ASSERT(pip != PipId());
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;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = net;
WireId dst = getPipDstWire(pip);
NPNR_ASSERT(wire_to_net[dst.index] == nullptr);
@ -757,7 +729,6 @@ struct Arch : BaseCtx
{
NPNR_ASSERT(pip != PipId());
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);
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] = nullptr;
// switches_locked[chip_info->pip_data[pip.index].switch_index] = nullptr;
refreshUiPip(pip);
refreshUiWire(dst);
}
@ -773,25 +743,6 @@ struct Arch : BaseCtx
bool checkPipAvail(PipId pip) const
{
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;
}
@ -806,7 +757,6 @@ struct Arch : BaseCtx
NetInfo *getConflictingPipNet(PipId pip) const
{
NPNR_ASSERT(pip != PipId());
// return switches_locked[chip_info->pip_data[pip.index].switch_index];
return pip_to_net[pip.index];
}
@ -820,14 +770,8 @@ struct Arch : BaseCtx
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.x = tile_info.getCol();
loc.y = tile_info.getRow();
loc.z = 0;
NPNR_ASSERT("TODO");
return loc;
}
@ -850,7 +794,9 @@ struct Arch : BaseCtx
WireId getPipDstWire(PipId pip) const
{
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
@ -985,6 +931,4 @@ struct Arch : BaseCtx
float placer_constraintWeight = 10;
};
// void ice40DelayFuzzerMain(Context *ctx);
NEXTPNR_NAMESPACE_END

View File

@ -29,43 +29,14 @@ NEXTPNR_NAMESPACE_BEGIN
bool Arch::logicCellsCompatible(const CellInfo **it, const size_t size) const
{
// bool dffs_exist = false, dffs_neg = false;
// 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;
// TODO: Check clock, clock-enable, and set-reset compatiility
return true;
}
bool Arch::isBelLocationValid(BelId bel) const
{
if (getBelType(bel) == id_ICESTORM_LC) {
std::array<const CellInfo *, 8> bel_cells;
if (getBelType(bel) == id("XC7_LC")) {
std::array<const CellInfo *, 4> bel_cells;
size_t num_cells = 0;
Loc bel_loc = getBelLocation(bel);
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
{
// if (cell->type == id_ICESTORM_LC) {
// NPNR_ASSERT(getBelType(bel) == id_ICESTORM_LC);
//
// 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)) {
// CellInfo *ci_other = getBoundBelCell(bel_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);
// } 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;
// }
if (cell->type == id("XC7_LC")) {
std::array<const CellInfo *, 4> bel_cells;
size_t num_cells = 0;
Loc bel_loc = getBelLocation(bel);
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
CellInfo *ci_other = getBoundBelCell(bel_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);
}
else {
return true;
}
}
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 "led3" LOCATE = SITE "D18" LEVEL 1;
COMP "clki" LOCATE = SITE "K17" LEVEL 1;
COMP "clk_gb" LOCATE = SITE "BUFGCTRL_X0Y31" LEVEL 1;
NET "clki" PERIOD = 8 nS ;
PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD;
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("CARRY_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_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(), "COUT", PORT_OUT);
} else if (type == ctx->id("IOBUF")) {
new_cell->type = id_IOB33;
new_cell->params[ctx->id("PIN_TYPE")] = "0";
new_cell->params[ctx->id("PULLUP")] = "0";
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);
if (ctx->args.type == ArchArgs::Z020)
new_cell->type = id_IOB33;
else
new_cell->type = id_IOB18;
add_port(ctx, new_cell.get(), "I", PORT_OUT);
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) {
add_port(ctx, new_cell.get(), "I0", PORT_IN);
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 {
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)
{
lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")];
replace_port(lut, ctx->id("I0"), lc, id_I6);
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);
int i = 6;
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) {
replace_port(lut, id_O, lc, id_O);
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";
std::string config = dff->type.str(ctx).substr(2);
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 == 'C' || *citer == 'P')
lc->params[ctx->id("ASYNC_SR")] = "1";
else
lc->params[ctx->id("ASYNC_SR")] = "0";
auto gnd_net = ctx->nets.at(ctx->id("$PACKER_GND_NET")).get();
if (*citer == 'S') {
citer++;
replace_port(dff, ctx->id("S"), lc, ctx->id("SR"));
lc->params[ctx->id("SET_NORESET")] = "1";
if (get_net_or_empty(dff, id_S) != gnd_net) {
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') {
citer++;
replace_port(dff, ctx->id("R"), lc, ctx->id("SR"));
lc->params[ctx->id("SET_NORESET")] = "0";
if (get_net_or_empty(dff, id_R) != gnd_net) {
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') {
citer++;
replace_port(dff, ctx->id("CLR"), lc, ctx->id("SR"));
lc->params[ctx->id("SET_NORESET")] = "0";
if (get_net_or_empty(dff, id_CLR) != gnd_net) {
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 {
NPNR_ASSERT(*citer == 'P');
citer++;
replace_port(dff, ctx->id("PRE"), lc, ctx->id("SR"));
lc->params[ctx->id("SET_NORESET")] = "1";
if (get_net_or_empty(dff, id_PRE) != gnd_net) {
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') {
replace_port(dff, ctx->id("CE"), lc, ctx->id("CE"));
auto vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
++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());
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);
}
@ -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"));
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)
@ -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)
{
if (port.cell == nullptr)
return false;
if (is_ff(ctx, port.cell))
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");
NPNR_ASSERT("TODO");
return false;
}
@ -400,13 +227,7 @@ bool is_reset_port(const BaseCtx *ctx, const PortRef &port)
{
if (port.cell == nullptr)
return false;
if (is_ff(ctx, port.cell))
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");
NPNR_ASSERT("TODO");
return false;
}
@ -414,13 +235,7 @@ bool is_enable_port(const BaseCtx *ctx, const PortRef &port)
{
if (port.cell == nullptr)
return false;
if (is_ff(ctx, port.cell))
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");
NPNR_ASSERT("TODO");
return false;
}

View File

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

View File

@ -25,106 +25,38 @@ NEXTPNR_NAMESPACE_BEGIN
#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
{
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_info = torc_info->tiles.getTileInfo(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
auto abs_delta_y = abs(src_info.getRow() - dst_info.getRow());
#if 1
auto div_LH = std::div(abs_delta_x, 12);
auto div_LV = std::div(abs_delta_y, 18);
auto div_LVB = std::div(div_LV.rem, 12);
auto div_H6 = std::div(div_LH.rem, 6);
auto div_V6 = std::div(div_LVB.rem, 6);
auto div_H4 = std::div(div_H6.rem, 4);
auto div_V4 = std::div(div_V6.rem, 4);
auto div_H2 = std::div(div_H4.rem, 2);
auto div_V2 = std::div(div_V4.rem, 2);
auto num_H1 = div_H2.rem;
auto num_V1 = div_V2.rem;
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 +
(num_H1 + num_V1) * 150;
#else
return std::max(150, 33 * abs_delta_x + 66 * abs_delta_y);
#endif
const auto &dst_loc = torc_info->tile_to_xy[dst_tw.getTileIndex()];
if (!torc_info->wire_is_global[src.index]) {
auto abs_delta_x = abs(dst_loc.first - src_loc.first);
auto abs_delta_y = abs(dst_loc.second - src_loc.second);
auto div_LH = std::div(abs_delta_x, 12);
auto div_LV = std::div(abs_delta_y, 18);
auto div_LVB = std::div(div_LV.rem, 12);
auto div_H6 = std::div(div_LH.rem, 6);
auto div_V6 = std::div(div_LVB.rem, 6);
auto div_H4 = std::div(div_H6.rem, 4);
auto div_V4 = std::div(div_V6.rem, 4);
auto div_H2 = std::div(div_H4.rem, 2);
auto div_V2 = std::div(div_V4.rem, 2);
auto num_H1 = div_H2.rem;
auto num_V1 = div_V2.rem;
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 +
(num_H1 + num_V1) * 150;
}
else {
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
@ -134,7 +66,6 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
auto sink_loc = getBelLocation(sink.cell->bel);
auto abs_delta_x = abs(driver_loc.x - sink_loc.x);
auto abs_delta_y = abs(driver_loc.y - sink_loc.y);
#if 1
auto div_LH = std::div(abs_delta_x, 12);
auto div_LV = std::div(abs_delta_y, 18);
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 +
(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;
#else
return std::max(150, 33 * abs_delta_x + 66 * abs_delta_y);
#endif
}
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 specific("Architecture specific options");
specific.add_options()("xc7z020", "set device type to xc7z020");
// specific.add_options()("package", po::value<std::string>(), "set device package");
specific.add_options()("z020", "set device type to xc7z020");
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()("xdl", po::value<std::string>(), "XDL file to write");
// specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
@ -97,6 +98,12 @@ std::unique_ptr<Context> Xc7CommandHandler::createContext()
chipArgs.package = "clg400";
}
if (vm.count("vx980")) {
chipArgs.type = ArchArgs::VX980;
chipArgs.package = "ffg1926";
}
if (chipArgs.type == ArchArgs::NONE) {
chipArgs.type = ArchArgs::Z020;
chipArgs.package = "clg400";

View File

@ -128,172 +128,15 @@ static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value)
// Pack carry logic
static void pack_carries(Context *ctx)
{
log_info("Packing carries..\n");
std::unordered_set<IdString> exhausted_cells;
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);
}
//log_info("Packing carries..\n");
// TODO
}
// "Pack" RAMs
static void pack_ram(Context *ctx)
{
log_info("Packing RAMs..\n");
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);
}
//log_info("Packing RAMs..\n");
// TODO
}
// 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') &&
!constval) {
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 {
uc->ports[user.port].net = constnet;
constnet->users.push_back(user);
@ -421,9 +256,9 @@ static void pack_io(Context *ctx)
}
} else {
// Create a IOBUF buffer
std::unique_ptr<CellInfo> ice_cell = create_xc7_cell(ctx, ctx->id("IOBUF"), ci->name.str(ctx));
nxio_to_sb(ctx, ci, ice_cell.get());
new_cells.push_back(std::move(ice_cell));
std::unique_ptr<CellInfo> xc7_cell = create_xc7_cell(ctx, ctx->id("IOBUF"), ci->name.str(ctx));
nxio_to_sb(ctx, ci, xc7_cell.get());
new_cells.push_back(std::move(xc7_cell));
sb = new_cells.back().get();
}
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))
return false;
return !is_sb_io(ctx, port.cell) && !is_sb_pll40(ctx, port.cell) && !is_sb_pll40_pad(ctx, port.cell) &&
port.cell->type != ctx->id("SB_GB");
return !is_sb_io(ctx, port.cell) && port.cell->type != id_BUFGCTRL;
}
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)) {
CellInfo *ci = cell.second;
if (is_sb_lfosc(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_LFOSC"), ci->name.str(ctx) + "_OSC");
packed_cells.insert(ci->name);
replace_port(ci, ctx->id("CLKLFEN"), packed.get(), ctx->id("CLKLFEN"));
replace_port(ci, ctx->id("CLKLFPU"), packed.get(), ctx->id("CLKLFPU"));
if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF_FABRIC"));
} else {
replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF"));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_hfosc(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_HFOSC"), ci->name.str(ctx) + "_OSC");
packed_cells.insert(ci->name);
packed->params[ctx->id("CLKHF_DIV")] = str_or_default(ci->params, ctx->id("CLKHF_DIV"), "0b00");
replace_port(ci, ctx->id("CLKHFEN"), packed.get(), ctx->id("CLKHFEN"));
replace_port(ci, ctx->id("CLKHFPU"), packed.get(), ctx->id("CLKHFPU"));
if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME
replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF_FABRIC"));
} else {
replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF"));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_spram(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_SPRAM"), ci->name.str(ctx) + "_RAM");
packed_cells.insert(ci->name);
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);
}
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_mac16(ctx, ci)) {
std::unique_ptr<CellInfo> packed =
create_xc7_cell(ctx, ctx->id("ICESTORM_DSP"), ci->name.str(ctx) + "_DSP");
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;
if (ci->type == id_BUFGCTRL) {
ci->params.emplace(ctx->id("PRESELECT_I0"), "FALSE");
ci->params.emplace(ctx->id("CE0INV"), "CE0");
ci->params.emplace(ctx->id("S0INV"), "S0");
ci->params.emplace(ctx->id("IGNORE0INV"), "IGNORE0");
ci->params.emplace(ctx->id("CE1INV"), "CE1");
ci->params.emplace(ctx->id("S1INV"), "S1");
ci->params.emplace(ctx->id("IGNORE1INV"), "IGNORE1");
} else if (ci->type == id_MMCME2_ADV) {
ci->params.emplace(ctx->id("BANDWIDTH"), "OPTIMIZED");
ci->params.emplace(ctx->id("CLKBURST_ENABLE"), "FALSE");
ci->params.emplace(ctx->id("CLKBURST_REPEAT"), "FALSE");
ci->params.emplace(ctx->id("CLKFBIN_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKFBIN_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKFBOUT_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKFBOUT_EN"), "TRUE");
ci->params.emplace(ctx->id("CLKFBOUT_FRAC_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_FALL"), "FALSE");
ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_RISE"), "FALSE");
ci->params.emplace(ctx->id("CLKFBOUT_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKFBOUT_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKINSELINV"), "CLKINSEL");
ci->params.emplace(ctx->id("CLKOUT0_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT0_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT0_FRAC_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_FALL"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_RISE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT0_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT0_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT1_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT1_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT1_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT1_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT2_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT2_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT2_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT2_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT3_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT3_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT3_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT3_USE_FINE_PS"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT4_CASCADE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT4_EDGE"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT4_EN"), "FALSE");
ci->params.emplace(ctx->id("CLKOUT4_NOCOUNT"), "TRUE");
ci->params.emplace(ctx->id("CLKOUT4_USE_FINE_PS"), "FALSE");
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) {
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));
ci->params[ctx->id("COMPENSATION")] = "INTERNAL";
}
}

View File

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

View File

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

View File

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

View File

@ -25,6 +25,7 @@
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include <boost/range/adaptor/reversed.hpp>
#include "torc/Physical.hpp"
using namespace torc::architecture::xilinx;
@ -32,9 +33,8 @@ using namespace torc::physical;
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", "");
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;
if (cell.second->type == id_SLICE_LUT6)
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);
else
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
if (lut_inputs.size() < 6) {
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())
value += init;
else {
@ -173,16 +173,21 @@ void write_xdl(const Context *ctx, std::ostream &out)
boost::replace_all(name, ":", "\\:");
instPtr->setConfig(setting, name, "#FF");
instPtr->setConfig(setting + "MUX", "", "O6");
instPtr->setConfig(setting + "INIT", "", "INIT" + cell.second->params.at(ctx->id("DFF_INIT")));
assert(cell.second->params.at(ctx->id("SET_NORESET")) == "0"); // TODO
instPtr->setConfig(setting + "SR", "", "SRLOW");
instPtr->setConfig(setting + "INIT", "", cell.second->params.at(ctx->id("FFINIT")));
NPNR_ASSERT(!cell.second->lcInfo.negClk); // TODO
instPtr->setConfig("CLKINV", "", "CLK");
if (cell.second->lcInfo.negClk)
instPtr->setConfig("CLKINV", "", "CLK_B");
else
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");
}
if (get_net_or_empty(cell.second.get(), ctx->id("CE")))
instPtr->setConfig("CEUSEDMUX", "", "IN");
instPtr->setConfig("SRUSEDMUX", "", "IN");
instPtr->setConfig("CEUSEDMUX", "", "IN");
instPtr->setConfig("SYNC_ATTR", "", "ASYNC");
}
} else if (cell.second->type == id_IOB33) {
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("SLEW", "", "SLOW");
}
} else if (cell.second->type == id_BUFGCTRL) {
auto it = cell.second->params.find(ctx->id("PRESELECT_I0"));
instPtr->setConfig("PRESELECT_I0", "", it != cell.second->params.end() ? it->second : "FALSE");
instPtr->setConfig("CE0INV", "", "CE0");
instPtr->setConfig("S0INV", "", "S0");
instPtr->setConfig("IGNORE0INV", "", "IGNORE0");
instPtr->setConfig("CE1INV", "", "CE1");
instPtr->setConfig("S1INV", "", "S1");
instPtr->setConfig("IGNORE1INV", "", "IGNORE1");
} else if (cell.second->type == id_PS7) {
} else if (cell.second->type == id_IOB18) {
if (get_net_or_empty(cell.second.get(), id_I)) {
instPtr->setConfig("IUSED", "", "0");
instPtr->setConfig("IBUF_LOW_PWR", "", "TRUE");
instPtr->setConfig("ISTANDARD", "", "LVCMOS18");
} else {
instPtr->setConfig("OUSED", "", "0");
instPtr->setConfig("OSTANDARD", "", "LVCMOS18");
instPtr->setConfig("DRIVE", "", "12");
instPtr->setConfig("SLEW", "", "SLOW");
}
} 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
log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx));
}
@ -224,38 +232,53 @@ void write_xdl(const Context *ctx, std::ostream &out)
const auto lut = bel_to_lut(driver.cell->bel);
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);
netPtr->addSource(pinPtr);
for (const auto &user : net.second->users) {
site_index = torc_info->bel_to_site_index[user.cell->bel.index];
instPtr = site_to_instance.at(site_index);
if (!net.second->users.empty()) {
for (const auto &user : net.second->users) {
site_index = torc_info->bel_to_site_index[user.cell->bel.index];
instPtr = site_to_instance.at(site_index);
pin_name = user.port.str(ctx);
// For all LUT based inputs and outputs (I1-I6,O,OQ,OMUX) then change the I/O into the LUT
if (user.cell->type == id_SLICE_LUT6 && (pin_name[0] == 'I' || pin_name[0] == 'O')) {
const auto lut = bel_to_lut(user.cell->bel);
pin_name[0] = lut[0];
pin_name = user.port.str(ctx);
// For all LUT based inputs and outputs (I1-I6,O,OQ,OMUX) then change the I/O into the LUT
if (user.cell->type == id_SLICE_LUT6 && (pin_name[0] == 'I' || pin_name[0] == 'O')) {
const auto lut = bel_to_lut(user.cell->bel);
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);
netPtr->addSink(pinPtr);
}
pinPtr = Factory::newInstancePinPtr(instPtr, pin_name);
netPtr->addSink(pinPtr);
}
auto b = designPtr->addNet(netPtr);
assert(b);
auto b = designPtr->addNet(netPtr);
assert(b);
for (const auto &i : net.second->wires) {
const auto &pip_map = i.second;
if (pip_map.pip == PipId())
continue;
ExtendedWireInfo ewi_src(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSourceTilewire());
ExtendedWireInfo ewi_dst(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSinkTilewire());
auto p = Factory::newPip(ewi_src.mTileName, ewi_src.mWireName, ewi_dst.mWireName,
ePipUnidirectionalBuffered);
netPtr->addPip(p);
for (const auto &i : net.second->wires) {
const auto &pip_map = i.second;
if (pip_map.pip == PipId())
continue;
ExtendedWireInfo ewi_src(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSourceTilewire());
ExtendedWireInfo ewi_dst(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSinkTilewire());
auto p = Factory::newPip(ewi_src.mTileName, ewi_src.mWireName, ewi_dst.mWireName,
ePipUnidirectionalBuffered);
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);
}