Merge branch 'xc7' into xc7_gui
This commit is contained in:
commit
ede0e93206
12
.cirrus.yml
Normal file
12
.cirrus.yml
Normal 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
|
55
.cirrus/Dockerfile.ubuntu16.04
Normal file
55
.cirrus/Dockerfile.ubuntu16.04
Normal 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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
||||
```
|
||||
|
@ -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) {
|
||||
|
@ -131,9 +131,9 @@ void CommandHandler::setupContext(Context *ctx)
|
||||
}
|
||||
|
||||
if (vm.count("quiet")) {
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING));
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG));
|
||||
} else {
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG));
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG));
|
||||
}
|
||||
|
||||
if (vm.count("log")) {
|
||||
@ -141,7 +141,7 @@ void CommandHandler::setupContext(Context *ctx)
|
||||
logfile = std::ofstream(logfilename);
|
||||
if (!logfile)
|
||||
log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str());
|
||||
log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG));
|
||||
log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG));
|
||||
}
|
||||
|
||||
if (vm.count("force")) {
|
||||
@ -285,8 +285,8 @@ void CommandHandler::conflicting_options(const boost::program_options::variables
|
||||
|
||||
void CommandHandler::printFooter()
|
||||
{
|
||||
int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING, 0),
|
||||
error_count = get_or_default(message_count_by_level, LogLevel::ERROR, 0);
|
||||
int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0),
|
||||
error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0);
|
||||
if (warning_count > 0 || error_count > 0)
|
||||
log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count,
|
||||
error_count == 1 ? "" : "s");
|
||||
|
@ -84,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap)
|
||||
return string;
|
||||
}
|
||||
|
||||
void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG)
|
||||
void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG)
|
||||
{
|
||||
//
|
||||
// Trim newlines from the beginning
|
||||
@ -132,7 +132,7 @@ void log_always(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap, LogLevel::ALWAYS);
|
||||
logv(format, ap, LogLevel::ALWAYS_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ void log(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap, LogLevel::LOG);
|
||||
logv(format, ap, LogLevel::LOG_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ void log_info(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("Info: ", format, ap, LogLevel::INFO);
|
||||
logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ void log_warning(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING);
|
||||
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ void log_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
|
||||
|
||||
if (log_error_atexit)
|
||||
log_error_atexit();
|
||||
@ -184,7 +184,7 @@ void log_nonfatal_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
|
||||
va_end(ap);
|
||||
had_nonfatal_error = true;
|
||||
}
|
||||
|
10
common/log.h
10
common/log.h
@ -44,11 +44,11 @@ struct log_execution_error_exception
|
||||
|
||||
enum class LogLevel
|
||||
{
|
||||
LOG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
ALWAYS
|
||||
LOG_MSG,
|
||||
INFO_MSG,
|
||||
WARNING_MSG,
|
||||
ERROR_MSG,
|
||||
ALWAYS_MSG
|
||||
};
|
||||
|
||||
extern std::vector<std::pair<std::ostream *, LogLevel>> log_streams;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
219
common/timing.cc
219
common/timing.cc
@ -86,6 +86,7 @@ struct CriticalPath
|
||||
};
|
||||
|
||||
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
|
||||
typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
|
||||
|
||||
struct Timing
|
||||
{
|
||||
@ -95,6 +96,7 @@ struct Timing
|
||||
delay_t min_slack;
|
||||
CriticalPathMap *crit_path;
|
||||
DelayFrequency *slack_histogram;
|
||||
NetCriticalityMap *net_crit;
|
||||
IdString async_clock;
|
||||
|
||||
struct TimingData
|
||||
@ -105,13 +107,15 @@ struct Timing
|
||||
unsigned max_path_length = 0;
|
||||
delay_t min_remaining_budget;
|
||||
bool false_startpoint = false;
|
||||
std::vector<delay_t> min_required;
|
||||
std::unordered_map<ClockEvent, delay_t> arrival_time;
|
||||
};
|
||||
|
||||
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
|
||||
DelayFrequency *slack_histogram = nullptr)
|
||||
DelayFrequency *slack_histogram = nullptr, NetCriticalityMap *net_crit = nullptr)
|
||||
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
|
||||
crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
|
||||
crit_path(crit_path), slack_histogram(slack_histogram), net_crit(net_crit),
|
||||
async_clock(ctx->id("$async$"))
|
||||
{
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
@ -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
624
common/timing_opt.cc
Normal 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
37
common/timing_opt.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nextpnr.h"
|
||||
#include "settings.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
struct TimingOptCfg : public Settings
|
||||
{
|
||||
TimingOptCfg(Context *ctx) : Settings(ctx) {}
|
||||
|
||||
// The timing optimiser will *only* optimise cells of these types
|
||||
// Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense
|
||||
// for other cell types
|
||||
std::unordered_set<IdString> cellTypes;
|
||||
};
|
||||
|
||||
extern bool timing_opt(Context *ctx, TimingOptCfg cfg);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
23
ecp5/arch.cc
23
ecp5/arch.cc
@ -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;
|
||||
}
|
||||
|
@ -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 ¶m : 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);
|
||||
|
@ -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"));
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
202
ecp5/pack.cc
202
ecp5/pack.cc
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
BIN
gui/resources/py.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false)
|
||||
|
||||
TaskManager::~TaskManager()
|
||||
{
|
||||
log_write_function = nullptr;
|
||||
if (workerThread.isRunning())
|
||||
terminate_thread();
|
||||
workerThread.quit();
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "nextpnr.h"
|
||||
#include "placer1.h"
|
||||
#include "router1.h"
|
||||
#include "timing_opt.h"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
@ -626,7 +627,18 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); }
|
||||
bool Arch::place()
|
||||
{
|
||||
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
|
||||
return false;
|
||||
if (bool_or_default(settings, id("opt_timing"), false)) {
|
||||
TimingOptCfg tocfg(getCtx());
|
||||
tocfg.cellTypes.insert(id_ICESTORM_LC);
|
||||
return timing_opt(getCtx(), tocfg);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
|
||||
|
||||
@ -950,8 +962,12 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
|
||||
if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
|
||||
return TMG_IGNORE;
|
||||
return TMG_ENDPOINT;
|
||||
} else if (cell->type == id_SB_LEDDA_IP) {
|
||||
if (port == id_CLK || port == id_CLOCK)
|
||||
return TMG_CLOCK_INPUT;
|
||||
return TMG_IGNORE;
|
||||
}
|
||||
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
|
||||
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this));
|
||||
}
|
||||
|
||||
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||
|
@ -156,7 +156,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
|
||||
// Lattice's weird string style params, not sure if
|
||||
// prefixes other than 0b should be supported, only 0b features in docs
|
||||
std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0");
|
||||
assert(raw.substr(0, 2) == "0b");
|
||||
if (raw.substr(0, 2) != "0b")
|
||||
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
|
||||
p.first.c_str(), cell->name.c_str(ctx));
|
||||
raw = raw.substr(2);
|
||||
value.resize(raw.length());
|
||||
for (int i = 0; i < (int)raw.length(); i++) {
|
||||
@ -168,7 +170,13 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int ival = get_param_or_def(cell, ctx->id(p.first), 0);
|
||||
int ival;
|
||||
try {
|
||||
ival = get_param_or_def(cell, ctx->id(p.first), 0);
|
||||
} catch (std::invalid_argument &e) {
|
||||
log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(),
|
||||
cell->name.c_str(ctx));
|
||||
}
|
||||
|
||||
for (int i = 0; i < p.second; i++)
|
||||
value.push_back((ival >> i) & 0x1);
|
||||
@ -486,7 +494,7 @@ void write_asc(const Context *ctx, std::ostream &out)
|
||||
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
|
||||
bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
|
||||
bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP"));
|
||||
bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT";
|
||||
bool lvds = cell.second->ioInfo.lvds;
|
||||
bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0;
|
||||
bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0;
|
||||
|
||||
@ -495,64 +503,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);
|
||||
|
@ -260,6 +260,21 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
|
||||
add_port(ctx, new_cell.get(), "RGB0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
|
||||
} else if (type == ctx->id("SB_LEDDA_IP")) {
|
||||
add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN);
|
||||
for (int i = 0; i < 8; i++)
|
||||
add_port(ctx, new_cell.get(), "LEDDDAT" + std::to_string(i), PORT_IN);
|
||||
for (int i = 0; i < 3; i++)
|
||||
add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); // doesn't actually exist, for icecube code compatibility
|
||||
// only
|
||||
add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "LEDDON", PORT_OUT);
|
||||
} else {
|
||||
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell-
|
||||
|
||||
inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); }
|
||||
|
||||
inline bool is_sb_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); }
|
||||
|
||||
inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
|
||||
{
|
||||
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
|
||||
|
107
ice40/chains.cc
107
ice40/chains.cc
@ -62,7 +62,7 @@ class ChainConstrainer
|
||||
bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) ||
|
||||
(int(chains.back().cells.size()) > max_length);
|
||||
if (split_chain) {
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
|
||||
CellInfo *passout = make_carry_pass_out((*(curr_cell - 1))->ports.at(ctx->id("COUT")));
|
||||
tile.pop_back();
|
||||
chains.back().cells.back() = passout;
|
||||
start_of_chain = true;
|
||||
@ -74,10 +74,10 @@ class ChainConstrainer
|
||||
(net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) !=
|
||||
net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) ||
|
||||
(at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) {
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")),
|
||||
at_end ? nullptr : *(curr_cell + 1));
|
||||
chains.back().cells.push_back(passout);
|
||||
tile.push_back(passout);
|
||||
start_of_chain = true;
|
||||
}
|
||||
}
|
||||
++curr_cell;
|
||||
@ -87,30 +87,75 @@ class ChainConstrainer
|
||||
}
|
||||
|
||||
// Insert a logic cell to legalise a COUT->fabric connection
|
||||
CellInfo *make_carry_pass_out(PortInfo &cout_port)
|
||||
CellInfo *make_carry_pass_out(PortInfo &cout_port, CellInfo *cin_cell = nullptr)
|
||||
{
|
||||
NPNR_ASSERT(cout_port.net != nullptr);
|
||||
std::unique_ptr<CellInfo> lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC"));
|
||||
lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3
|
||||
lc->params[ctx->id("CARRY_ENABLE")] = "1";
|
||||
lc->ports.at(ctx->id("O")).net = cout_port.net;
|
||||
lc->ports.at(id_O).net = cout_port.net;
|
||||
std::unique_ptr<NetInfo> co_i3_net(new NetInfo());
|
||||
co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3");
|
||||
co_i3_net->driver = cout_port.net->driver;
|
||||
PortRef i3_r;
|
||||
i3_r.port = ctx->id("I3");
|
||||
i3_r.port = id_I3;
|
||||
i3_r.cell = lc.get();
|
||||
co_i3_net->users.push_back(i3_r);
|
||||
PortRef o_r;
|
||||
o_r.port = ctx->id("O");
|
||||
o_r.port = id_O;
|
||||
o_r.cell = lc.get();
|
||||
cout_port.net->driver = o_r;
|
||||
lc->ports.at(ctx->id("I3")).net = co_i3_net.get();
|
||||
lc->ports.at(id_I3).net = co_i3_net.get();
|
||||
cout_port.net = co_i3_net.get();
|
||||
|
||||
IdString co_i3_name = co_i3_net->name;
|
||||
NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end());
|
||||
ctx->nets[co_i3_name] = std::move(co_i3_net);
|
||||
|
||||
// If COUT also connects to a CIN; preserve the carry chain
|
||||
if (cin_cell) {
|
||||
std::unique_ptr<NetInfo> co_cin_net(new NetInfo());
|
||||
co_cin_net->name = ctx->id(lc->name.str(ctx) + "$COUT");
|
||||
|
||||
// Connect I1 to 1 to preserve carry chain
|
||||
NetInfo *vcc = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
|
||||
lc->ports.at(id_I1).net = vcc;
|
||||
PortRef i1_r;
|
||||
i1_r.port = id_I1;
|
||||
i1_r.cell = lc.get();
|
||||
vcc->users.push_back(i1_r);
|
||||
|
||||
// Connect co_cin_net to the COUT of the LC
|
||||
PortRef co_r;
|
||||
co_r.port = id_COUT;
|
||||
co_r.cell = lc.get();
|
||||
co_cin_net->driver = co_r;
|
||||
lc->ports.at(id_COUT).net = co_cin_net.get();
|
||||
|
||||
// Find the user corresponding to the next CIN
|
||||
int replaced_ports = 0;
|
||||
if (ctx->debug)
|
||||
log_info("cell: %s\n", cin_cell->name.c_str(ctx));
|
||||
for (auto port : {id_CIN, id_I3}) {
|
||||
auto &usr = lc->ports.at(id_O).net->users;
|
||||
if (ctx->debug)
|
||||
for (auto user : usr)
|
||||
log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));
|
||||
auto fnd_user = std::find_if(usr.begin(), usr.end(),
|
||||
[&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; });
|
||||
if (fnd_user != usr.end()) {
|
||||
co_cin_net->users.push_back(*fnd_user);
|
||||
usr.erase(fnd_user);
|
||||
cin_cell->ports.at(port).net = co_cin_net.get();
|
||||
++replaced_ports;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT(replaced_ports > 0);
|
||||
IdString co_cin_name = co_cin_net->name;
|
||||
NPNR_ASSERT(ctx->nets.find(co_cin_name) == ctx->nets.end());
|
||||
ctx->nets[co_cin_name] = std::move(co_cin_net);
|
||||
}
|
||||
|
||||
IdString name = lc->name;
|
||||
ctx->assignCellInfo(lc.get());
|
||||
ctx->cells[lc->name] = std::move(lc);
|
||||
@ -163,29 +208,31 @@ class ChainConstrainer
|
||||
|
||||
void process_carries()
|
||||
{
|
||||
std::vector<CellChain> carry_chains =
|
||||
find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
|
||||
[](const Context *ctx, const
|
||||
std::vector<CellChain> carry_chains = find_chains(
|
||||
ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
|
||||
[](const Context *ctx, const
|
||||
|
||||
CellInfo *cell) {
|
||||
CellInfo *carry_prev =
|
||||
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
|
||||
if (carry_prev != nullptr)
|
||||
return carry_prev;
|
||||
/*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc,
|
||||
ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/
|
||||
return (CellInfo *)nullptr;
|
||||
},
|
||||
[](const Context *ctx, const CellInfo *cell) {
|
||||
CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc,
|
||||
ctx->id("CIN"), false);
|
||||
if (carry_next != nullptr)
|
||||
return carry_next;
|
||||
/*CellInfo *i3_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"),
|
||||
false); if (i3_next != nullptr) return i3_next;*/
|
||||
return (CellInfo *)nullptr;
|
||||
});
|
||||
CellInfo *cell) {
|
||||
CellInfo *carry_prev =
|
||||
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
|
||||
if (carry_prev != nullptr)
|
||||
return carry_prev;
|
||||
CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT"));
|
||||
if (i3_prev != nullptr)
|
||||
return i3_prev;
|
||||
return (CellInfo *)nullptr;
|
||||
},
|
||||
[](const Context *ctx, const CellInfo *cell) {
|
||||
CellInfo *carry_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false);
|
||||
if (carry_next != nullptr)
|
||||
return carry_next;
|
||||
CellInfo *i3_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false);
|
||||
if (i3_next != nullptr)
|
||||
return i3_next;
|
||||
return (CellInfo *)nullptr;
|
||||
});
|
||||
std::unordered_set<IdString> chained;
|
||||
for (auto &base_chain : carry_chains) {
|
||||
for (auto c : base_chain.cells)
|
||||
|
@ -65,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[])
|
||||
|
346
ice40/pack.cc
346
ice40/pack.cc
@ -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();
|
||||
|
48
ice40/pcf.cc
48
ice40/pcf.cc
@ -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) {
|
||||
|
@ -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
6
ice40/smoketest/attosoc/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
attosoc_pnr.v
|
||||
attosoc.asc
|
||||
attosoc.json
|
||||
attosoc_pnr_tb
|
||||
testbench.vcd
|
||||
output.txt
|
10
ice40/smoketest/attosoc/attosoc.pcf
Normal file
10
ice40/smoketest/attosoc/attosoc.pcf
Normal 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
|
||||
|
127
ice40/smoketest/attosoc/attosoc.v
Normal file
127
ice40/smoketest/attosoc/attosoc.v
Normal 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
|
32
ice40/smoketest/attosoc/attosoc_tb.v
Normal file
32
ice40/smoketest/attosoc/attosoc_tb.v
Normal 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
|
6
ice40/smoketest/attosoc/firmware.hex
Normal file
6
ice40/smoketest/attosoc/firmware.hex
Normal 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
|
16
ice40/smoketest/attosoc/golden.txt
Normal file
16
ice40/smoketest/attosoc/golden.txt
Normal file
@ -0,0 +1,16 @@
|
||||
00000000
|
||||
00000010
|
||||
00000011
|
||||
00000101
|
||||
00000111
|
||||
00001011
|
||||
00001101
|
||||
00010001
|
||||
00010011
|
||||
00010111
|
||||
00011101
|
||||
00011111
|
||||
00100101
|
||||
00101001
|
||||
00101011
|
||||
00101111
|
2979
ice40/smoketest/attosoc/picorv32.v
Normal file
2979
ice40/smoketest/attosoc/picorv32.v
Normal file
File diff suppressed because it is too large
Load Diff
9
ice40/smoketest/attosoc/smoketest.sh
Executable file
9
ice40/smoketest/attosoc/smoketest.sh
Executable 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
|
@ -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
177
xc7/125MHz_to_60MHz.v
Executable 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
|
509
xc7/arch.cc
509
xc7/arch.cc
@ -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 ¤tSegment = 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 ¤tTilewire = 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
|
||||
|
74
xc7/arch.h
74
xc7/arch.h
@ -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
|
||||
|
@ -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
9
xc7/attosoc.pcf
Normal 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
12
xc7/attosoc.sh
Executable 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
127
xc7/attosoc.v
Normal 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
56
xc7/attosoc.ys
Normal 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
25
xc7/attosoc_tb.vhd
Normal 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
15
xc7/attosoc_top.v
Normal 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
|
||||
|
@ -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;
|
||||
|
297
xc7/cells.cc
297
xc7/cells.cc
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
128
xc7/delay.cc
128
xc7/delay.cc
@ -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
8
xc7/firmware.hex
Normal 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
|
11
xc7/main.cc
11
xc7/main.cc
@ -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";
|
||||
|
640
xc7/pack.cc
640
xc7/pack.cc
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,6 @@
|
||||
read_verilog picorv32.v
|
||||
read_verilog picorv32_top.v
|
||||
read_verilog 125MHz_to_60MHz.v
|
||||
|
||||
#synth_xilinx -top picorv32
|
||||
|
||||
|
@ -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),
|
||||
|
113
xc7/xdl.cc
113
xc7/xdl.cc
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user