Merge https://github.com/YosysHQ/nextpnr into xc7
This commit is contained in:
commit
664c48f5e4
@ -121,14 +121,14 @@ if (BUILD_PYTHON)
|
||||
set(version ${PYTHONLIBS_VERSION_STRING})
|
||||
|
||||
STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version})
|
||||
find_package(Boost COMPONENTS "python-py${boost_py_version}" ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs})
|
||||
set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND})
|
||||
|
||||
while (NOT "${version}" STREQUAL "" AND NOT Boost_PYTHON_FOUND)
|
||||
STRING(REGEX REPLACE "([0-9.]+).[0-9]+" "\\1" version ${version})
|
||||
|
||||
STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version})
|
||||
find_package(Boost COMPONENTS "python-py${boost_py_version}" ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs})
|
||||
set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND})
|
||||
|
||||
STRING(REGEX MATCHALL "([0-9.]+).[0-9]+" has_more_version ${version})
|
||||
@ -138,21 +138,21 @@ if (BUILD_PYTHON)
|
||||
endwhile ()
|
||||
|
||||
if (NOT Boost_PYTHON_FOUND)
|
||||
find_package(Boost COMPONENTS python3 ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS python3 ${boost_libs})
|
||||
if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
|
||||
set(Boost_PYTHON_FOUND TRUE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT Boost_PYTHON_FOUND)
|
||||
find_package(Boost COMPONENTS python36 ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS python36 ${boost_libs})
|
||||
if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
|
||||
set(Boost_PYTHON_FOUND TRUE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT Boost_PYTHON_FOUND)
|
||||
find_package(Boost COMPONENTS python37 ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS python37 ${boost_libs})
|
||||
if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
|
||||
set(Boost_PYTHON_FOUND TRUE)
|
||||
endif ()
|
||||
@ -160,7 +160,7 @@ if (BUILD_PYTHON)
|
||||
|
||||
if (NOT Boost_PYTHON_FOUND)
|
||||
STRING(REGEX REPLACE "([0-9]+\\.[0-9]+).*" "\\1" gentoo_version ${PYTHONLIBS_VERSION_STRING})
|
||||
find_package(Boost COMPONENTS python-${gentoo_version} ${boost_libs})
|
||||
find_package(Boost QUIET COMPONENTS python-${gentoo_version} ${boost_libs})
|
||||
if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
|
||||
set(Boost_PYTHON_FOUND TRUE)
|
||||
endif ()
|
||||
|
12
README.md
12
README.md
@ -35,7 +35,7 @@ of the selected architecture:
|
||||
- Qt5 or later (`qt5-default` for Ubuntu 16.04)
|
||||
- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu)
|
||||
- on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL)
|
||||
- Boost libraries (`libboost-dev` or `libboost-all-dev` for Ubuntu)
|
||||
- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
|
||||
- Latest git Yosys is required to synthesise the demo design
|
||||
- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
|
||||
- For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base`
|
||||
@ -49,7 +49,8 @@ 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`.
|
||||
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).
|
||||
Then build and install `nextpnr-ice40` using the following commands:
|
||||
|
||||
```
|
||||
@ -96,8 +97,6 @@ sudo make install
|
||||
|
||||
- More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples).
|
||||
|
||||
- Currently the ECP5 flow supports LUTs, flipflops and IO. IO must be instantiated using `TRELLIS_IO` primitives and constraints specified
|
||||
using `LOC` and `IO_TYPE` attributes on those instances, as is used in the examples.
|
||||
|
||||
### nextpnr-generic
|
||||
|
||||
@ -116,7 +115,7 @@ Additional notes for building nextpnr
|
||||
|
||||
Use cmake `-D` options to specify which version of nextpnr you want to build.
|
||||
|
||||
Use `-DARCH=...` to set the architecture. It is semicolon separated list.
|
||||
Use `-DARCH=...` to set the architecture. It is a semicolon separated list.
|
||||
Use `cmake . -DARCH=all` to build all supported architectures.
|
||||
|
||||
The following runs a debug build of the iCE40 architecture without GUI
|
||||
@ -134,6 +133,9 @@ cmake -DARCH=ice40 -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DSTATIC_BUILD=ON .
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
You can change the location where nextpnr will be installed (this will usually default to `/usr/local`) by using
|
||||
`-DCMAKE_INSTALL_PREFIX=/install/prefix`.
|
||||
|
||||
Notes for developers
|
||||
--------------------
|
||||
|
||||
|
@ -36,11 +36,12 @@
|
||||
#include "jsonparse.h"
|
||||
#include "log.h"
|
||||
#include "timing.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) { log_files.push_back(stdout); }
|
||||
CommandHandler::CommandHandler(int argc, char **argv) : argc(argc), argv(argv) { log_streams.clear(); }
|
||||
|
||||
bool CommandHandler::parseOptions()
|
||||
{
|
||||
@ -64,14 +65,14 @@ bool CommandHandler::parseOptions()
|
||||
bool CommandHandler::executeBeforeContext()
|
||||
{
|
||||
if (vm.count("help") || argc == 1) {
|
||||
std::cout << boost::filesystem::basename(argv[0])
|
||||
std::cerr << boost::filesystem::basename(argv[0])
|
||||
<< " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||
std::cout << options << "\n";
|
||||
std::cerr << options << "\n";
|
||||
return argc != 1;
|
||||
}
|
||||
|
||||
if (vm.count("version")) {
|
||||
std::cout << boost::filesystem::basename(argv[0])
|
||||
std::cerr << boost::filesystem::basename(argv[0])
|
||||
<< " -- Next Generation Place and Route (git sha1 " GIT_COMMIT_HASH_STR ")\n";
|
||||
return true;
|
||||
}
|
||||
@ -84,7 +85,9 @@ po::options_description CommandHandler::getGeneralOptions()
|
||||
po::options_description general("General options");
|
||||
general.add_options()("help,h", "show help");
|
||||
general.add_options()("verbose,v", "verbose output");
|
||||
general.add_options()("quiet,q", "quiet mode, only errors displayed");
|
||||
general.add_options()("quiet,q", "quiet mode, only errors and warnings displayed");
|
||||
general.add_options()("log,l", po::value<std::string>(),
|
||||
"log file, all log messages are written to this file regardless of -q");
|
||||
general.add_options()("debug", "debug output");
|
||||
general.add_options()("force,f", "keep running after errors");
|
||||
#ifndef NO_GUI
|
||||
@ -128,7 +131,17 @@ void CommandHandler::setupContext(Context *ctx)
|
||||
}
|
||||
|
||||
if (vm.count("quiet")) {
|
||||
log_quiet_warnings = true;
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING));
|
||||
} else {
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG));
|
||||
}
|
||||
|
||||
if (vm.count("log")) {
|
||||
std::string logfilename = vm["log"].as<std::string>();
|
||||
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));
|
||||
}
|
||||
|
||||
if (vm.count("force")) {
|
||||
@ -258,7 +271,7 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
||||
deinit_python();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
return had_nonfatal_error ? 1 : 0;
|
||||
}
|
||||
|
||||
void CommandHandler::conflicting_options(const boost::program_options::variables_map &vm, const char *opt1,
|
||||
@ -270,6 +283,15 @@ 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);
|
||||
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");
|
||||
}
|
||||
|
||||
int CommandHandler::exec()
|
||||
{
|
||||
try {
|
||||
@ -288,8 +310,11 @@ int CommandHandler::exec()
|
||||
settings = std::unique_ptr<Settings>(new Settings(ctx.get()));
|
||||
setupContext(ctx.get());
|
||||
setupArchContext(ctx.get());
|
||||
return executeMain(std::move(ctx));
|
||||
int rc = executeMain(std::move(ctx));
|
||||
printFooter();
|
||||
return rc;
|
||||
} catch (log_execution_error_exception) {
|
||||
printFooter();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ class CommandHandler
|
||||
int executeMain(std::unique_ptr<Context> ctx);
|
||||
po::options_description getGeneralOptions();
|
||||
void run_script_hook(const std::string &name);
|
||||
void printFooter();
|
||||
|
||||
protected:
|
||||
po::variables_map vm;
|
||||
@ -66,6 +67,7 @@ class CommandHandler
|
||||
int argc;
|
||||
char **argv;
|
||||
ProjectHandler project;
|
||||
std::ofstream logfile;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
131
common/log.cc
131
common/log.cc
@ -32,19 +32,15 @@ NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
NPNR_NORETURN void logv_error(const char *format, va_list ap) NPNR_ATTRIBUTE(noreturn);
|
||||
|
||||
std::vector<FILE *> log_files;
|
||||
std::vector<std::ostream *> log_streams;
|
||||
FILE *log_errfile = NULL;
|
||||
std::vector<std::pair<std::ostream *, LogLevel>> log_streams;
|
||||
log_write_type log_write_function = nullptr;
|
||||
|
||||
bool log_error_stderr = false;
|
||||
bool log_cmd_error_throw = false;
|
||||
bool log_quiet_warnings = false;
|
||||
std::string log_last_error;
|
||||
void (*log_error_atexit)() = NULL;
|
||||
|
||||
// static bool next_print_log = false;
|
||||
std::unordered_map<LogLevel, int> message_count_by_level;
|
||||
static int log_newline_count = 0;
|
||||
bool had_nonfatal_error = false;
|
||||
|
||||
std::string stringf(const char *fmt, ...)
|
||||
{
|
||||
@ -88,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap)
|
||||
return string;
|
||||
}
|
||||
|
||||
void logv(const char *format, va_list ap)
|
||||
void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG)
|
||||
{
|
||||
//
|
||||
// Trim newlines from the beginning
|
||||
@ -108,90 +104,51 @@ void logv(const char *format, va_list ap)
|
||||
else
|
||||
log_newline_count = str.size() - nnl_pos - 1;
|
||||
|
||||
for (auto f : log_files)
|
||||
fputs(str.c_str(), f);
|
||||
|
||||
for (auto f : log_streams)
|
||||
*f << str;
|
||||
if (f.second <= level)
|
||||
*f.first << str;
|
||||
if (log_write_function)
|
||||
log_write_function(str);
|
||||
}
|
||||
|
||||
void logv_info(const char *format, va_list ap)
|
||||
void log_with_level(LogLevel level, const char *format, ...)
|
||||
{
|
||||
message_count_by_level[level]++;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap, level);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void logv_prefixed(const char *prefix, const char *format, va_list ap, LogLevel level)
|
||||
{
|
||||
std::string message = vstringf(format, ap);
|
||||
|
||||
log_always("Info: %s", message.c_str());
|
||||
log_with_level(level, "%s%s", prefix, message.c_str());
|
||||
log_flush();
|
||||
}
|
||||
|
||||
void logv_warning(const char *format, va_list ap)
|
||||
{
|
||||
std::string message = vstringf(format, ap);
|
||||
|
||||
log_always("Warning: %s", message.c_str());
|
||||
log_flush();
|
||||
}
|
||||
|
||||
void logv_warning_noprefix(const char *format, va_list ap)
|
||||
{
|
||||
std::string message = vstringf(format, ap);
|
||||
|
||||
log_always("%s", message.c_str());
|
||||
}
|
||||
|
||||
void logv_error(const char *format, va_list ap)
|
||||
{
|
||||
#ifdef EMSCRIPTEN
|
||||
auto backup_log_files = log_files;
|
||||
#endif
|
||||
|
||||
if (log_errfile != NULL)
|
||||
log_files.push_back(log_errfile);
|
||||
|
||||
if (log_error_stderr)
|
||||
for (auto &f : log_files)
|
||||
if (f == stdout)
|
||||
f = stderr;
|
||||
|
||||
log_last_error = vstringf(format, ap);
|
||||
log_always("ERROR: %s", log_last_error.c_str());
|
||||
log_flush();
|
||||
|
||||
if (log_error_atexit)
|
||||
log_error_atexit();
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
log_files = backup_log_files;
|
||||
#endif
|
||||
throw log_execution_error_exception();
|
||||
}
|
||||
|
||||
void log_always(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap);
|
||||
logv(format, ap, LogLevel::ALWAYS);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void log(const char *format, ...)
|
||||
{
|
||||
if (log_quiet_warnings)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap);
|
||||
logv(format, ap, LogLevel::LOG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void log_info(const char *format, ...)
|
||||
{
|
||||
if (log_quiet_warnings)
|
||||
return;
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_info(format, ap);
|
||||
logv_prefixed("Info: ", format, ap, LogLevel::INFO);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -199,15 +156,7 @@ void log_warning(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_warning(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void log_warning_noprefix(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_warning_noprefix(format, ap);
|
||||
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -215,41 +164,35 @@ void log_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_error(format, ap);
|
||||
}
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
|
||||
void log_cmd_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
if (log_error_atexit)
|
||||
log_error_atexit();
|
||||
|
||||
if (log_cmd_error_throw) {
|
||||
log_last_error = vstringf(format, ap);
|
||||
log_always("ERROR: %s", log_last_error.c_str());
|
||||
log_flush();
|
||||
throw log_cmd_error_exception();
|
||||
}
|
||||
|
||||
logv_error(format, ap);
|
||||
throw log_execution_error_exception();
|
||||
}
|
||||
|
||||
void log_break()
|
||||
{
|
||||
if (log_quiet_warnings)
|
||||
return;
|
||||
if (log_newline_count < 2)
|
||||
log_always("\n");
|
||||
log("\n");
|
||||
if (log_newline_count < 2)
|
||||
log_always("\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void log_nonfatal_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
va_end(ap);
|
||||
had_nonfatal_error = true;
|
||||
}
|
||||
|
||||
void log_flush()
|
||||
{
|
||||
for (auto f : log_files)
|
||||
fflush(f);
|
||||
|
||||
for (auto f : log_streams)
|
||||
f->flush();
|
||||
f.first->flush();
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
23
common/log.h
23
common/log.h
@ -26,8 +26,8 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "nextpnr.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
@ -42,14 +42,22 @@ struct log_execution_error_exception
|
||||
{
|
||||
};
|
||||
|
||||
extern std::vector<FILE *> log_files;
|
||||
extern std::vector<std::ostream *> log_streams;
|
||||
extern FILE *log_errfile;
|
||||
enum class LogLevel
|
||||
{
|
||||
LOG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
ALWAYS
|
||||
};
|
||||
|
||||
extern std::vector<std::pair<std::ostream *, LogLevel>> log_streams;
|
||||
extern log_write_type log_write_function;
|
||||
|
||||
extern bool log_quiet_warnings;
|
||||
extern std::string log_last_error;
|
||||
extern void (*log_error_atexit)();
|
||||
extern bool had_nonfatal_error;
|
||||
extern std::unordered_map<LogLevel, int> message_count_by_level;
|
||||
|
||||
std::string stringf(const char *fmt, ...);
|
||||
std::string vstringf(const char *fmt, va_list ap);
|
||||
@ -59,10 +67,8 @@ void log(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
void log_always(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
void log_info(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
void log_warning(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
void log_warning_noprefix(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
NPNR_NORETURN void log_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn);
|
||||
NPNR_NORETURN void log_cmd_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2), noreturn);
|
||||
|
||||
void log_nonfatal_error(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
|
||||
void log_break();
|
||||
void log_flush();
|
||||
|
||||
@ -75,7 +81,6 @@ static inline void log_assert_worker(bool cond, const char *expr, const char *fi
|
||||
NEXTPNR_NAMESPACE_PREFIX log_assert_worker(_assert_expr_, #_assert_expr_, __FILE__, __LINE__)
|
||||
|
||||
#define log_abort() log_error("Abort in %s:%d.\n", __FILE__, __LINE__)
|
||||
#define log_ping() log("-- %s:%d %s --\n", __FILE__, __LINE__, __PRETTY_FUNCTION__)
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
|
@ -409,12 +409,16 @@ void Context::check() const
|
||||
|
||||
void BaseCtx::addClock(IdString net, float freq)
|
||||
{
|
||||
log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq);
|
||||
std::unique_ptr<ClockConstraint> cc(new ClockConstraint());
|
||||
cc->period = getCtx()->getDelayFromNS(1000 / freq);
|
||||
cc->high = getCtx()->getDelayFromNS(500 / freq);
|
||||
cc->low = getCtx()->getDelayFromNS(500 / freq);
|
||||
if (!nets.count(net)) {
|
||||
log_warning("net '%s' does not exist in design, ignoring clock constraint\n", net.c_str(this));
|
||||
} else {
|
||||
nets.at(net)->clkconstr = std::move(cc);
|
||||
log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq);
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -337,9 +337,10 @@ class SAPlacer
|
||||
}
|
||||
} else {
|
||||
uint64_t score = ctx->rng64();
|
||||
if (score <= best_ripup_score) {
|
||||
CellInfo *bound_cell = ctx->getBoundBelCell(bel);
|
||||
if (score <= best_ripup_score && bound_cell->belStrength < STRENGTH_STRONG) {
|
||||
best_ripup_score = score;
|
||||
ripup_target = ctx->getBoundBelCell(bel);
|
||||
ripup_target = bound_cell;
|
||||
ripup_bel = bel;
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,19 @@ void translate_assertfail(const assertion_failure &e)
|
||||
PyErr_SetString(PyExc_AssertionError, e.what());
|
||||
}
|
||||
|
||||
namespace PythonConversion {
|
||||
template <> struct string_converter<PortRef &>
|
||||
{
|
||||
inline PortRef from_str(Context *ctx, std::string name) { NPNR_ASSERT_FALSE("PortRef from_str not implemented"); }
|
||||
|
||||
inline std::string to_str(Context *ctx, const PortRef &pr)
|
||||
{
|
||||
return pr.cell->name.str(ctx) + "." + pr.port.str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace PythonConversion
|
||||
|
||||
BOOST_PYTHON_MODULE(MODULE_NAME)
|
||||
{
|
||||
register_exception_translator<assertion_failure>(&translate_assertfail);
|
||||
@ -120,7 +133,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
||||
readwrite_wrapper<PortInfo &, decltype(&PortInfo::type), &PortInfo::type, pass_through<PortType>,
|
||||
pass_through<PortType>>::def_wrap(pi_cls, "type");
|
||||
|
||||
typedef std::vector<PortRef> PortVector;
|
||||
typedef std::vector<PortRef> PortRefVector;
|
||||
typedef std::unordered_map<WireId, PipMap> WireMap;
|
||||
|
||||
auto ni_cls = class_<ContextualWrapper<NetInfo &>>("NetInfo", no_init);
|
||||
@ -128,7 +141,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
||||
conv_from_str<IdString>>::def_wrap(ni_cls, "name");
|
||||
readwrite_wrapper<NetInfo &, decltype(&NetInfo::driver), &NetInfo::driver, wrap_context<PortRef &>,
|
||||
unwrap_context<PortRef &>>::def_wrap(ni_cls, "driver");
|
||||
readonly_wrapper<NetInfo &, decltype(&NetInfo::users), &NetInfo::users, wrap_context<PortVector &>>::def_wrap(
|
||||
readonly_wrapper<NetInfo &, decltype(&NetInfo::users), &NetInfo::users, wrap_context<PortRefVector &>>::def_wrap(
|
||||
ni_cls, "users");
|
||||
readonly_wrapper<NetInfo &, decltype(&NetInfo::wires), &NetInfo::wires, wrap_context<WireMap &>>::def_wrap(ni_cls,
|
||||
"wires");
|
||||
@ -141,12 +154,21 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
||||
readwrite_wrapper<PortRef &, decltype(&PortRef::budget), &PortRef::budget, pass_through<delay_t>,
|
||||
pass_through<delay_t>>::def_wrap(pr_cls, "budget");
|
||||
|
||||
auto pm_cls = class_<ContextualWrapper<PipMap &>>("PipMap", no_init);
|
||||
readwrite_wrapper<PipMap &, decltype(&PipMap::pip), &PipMap::pip, conv_to_str<PipId>,
|
||||
conv_from_str<PipId>>::def_wrap(pm_cls, "pip");
|
||||
readwrite_wrapper<PipMap &, decltype(&PipMap::strength), &PipMap::strength, pass_through<PlaceStrength>,
|
||||
pass_through<PlaceStrength>>::def_wrap(pm_cls, "strength");
|
||||
|
||||
def("parse_json", parse_json_shim);
|
||||
def("load_design", load_design_shim, return_value_policy<manage_new_object>());
|
||||
|
||||
WRAP_MAP(AttrMap, pass_through<std::string>, "AttrMap");
|
||||
WRAP_MAP(PortMap, wrap_context<PortInfo &>, "PortMap");
|
||||
WRAP_MAP(PinMap, conv_to_str<IdString>, "PinMap");
|
||||
WRAP_MAP(WireMap, wrap_context<PipMap &>, "WireMap");
|
||||
|
||||
WRAP_VECTOR(PortRefVector, wrap_context<PortRef &>);
|
||||
|
||||
arch_wrap_python();
|
||||
}
|
||||
|
@ -118,6 +118,63 @@ struct range_wrapper
|
||||
#define WRAP_RANGE(t, conv) \
|
||||
range_wrapper<t##Range, return_value_policy<return_by_value>, conv>().wrap(#t "Range", #t "Iterator")
|
||||
|
||||
/*
|
||||
A wrapper for a vector or similar structure. With support for conversion
|
||||
*/
|
||||
|
||||
template <typename T, typename P = return_value_policy<return_by_value>,
|
||||
typename value_conv = PythonConversion::pass_through<T>>
|
||||
struct vector_wrapper
|
||||
{
|
||||
typedef decltype(std::declval<T>().begin()) iterator_t;
|
||||
typedef decltype(*(std::declval<iterator_t>())) value_t;
|
||||
typedef typename PythonConversion::ContextualWrapper<T &> wrapped_vector;
|
||||
typedef typename PythonConversion::ContextualWrapper<std::pair<iterator_t, iterator_t>> wrapped_pair;
|
||||
using return_t = typename value_conv::ret_type;
|
||||
static wrapped_pair iter(wrapped_vector &range)
|
||||
{
|
||||
return wrapped_pair(range.ctx, std::make_pair(range.base.begin(), range.base.end()));
|
||||
}
|
||||
|
||||
static std::string repr(wrapped_vector &range)
|
||||
{
|
||||
PythonConversion::string_converter<value_t> conv;
|
||||
bool first = true;
|
||||
std::stringstream ss;
|
||||
ss << "[";
|
||||
for (const auto &item : range.base) {
|
||||
if (!first)
|
||||
ss << ", ";
|
||||
ss << "'" << conv.to_str(range.ctx, item) << "'";
|
||||
first = false;
|
||||
}
|
||||
ss << "]";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static int len(wrapped_vector &range) { return range.base.size(); }
|
||||
|
||||
static return_t getitem(wrapped_vector &range, int i)
|
||||
{
|
||||
return value_conv()(range.ctx, boost::ref(range.base.at(i)));
|
||||
}
|
||||
|
||||
static void wrap(const char *range_name, const char *iter_name)
|
||||
{
|
||||
class_<wrapped_vector>(range_name, no_init)
|
||||
.def("__iter__", iter)
|
||||
.def("__repr__", repr)
|
||||
.def("__len__", len)
|
||||
.def("__getitem__", getitem);
|
||||
|
||||
iterator_wrapper<iterator_t, P, value_conv>().wrap(iter_name);
|
||||
}
|
||||
|
||||
typedef iterator_wrapper<iterator_t, P, value_conv> iter_wrap;
|
||||
};
|
||||
|
||||
#define WRAP_VECTOR(t, conv) vector_wrapper<t, return_value_policy<return_by_value>, conv>().wrap(#t, #t "Iterator")
|
||||
|
||||
/*
|
||||
Wrapper for a pair, allows accessing either using C++-style members (.first and
|
||||
.second) or as a Python iterable and indexable object
|
||||
|
@ -812,7 +812,8 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
|
||||
#endif
|
||||
|
||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||
timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */);
|
||||
timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */,
|
||||
true /* warn_on_failure */);
|
||||
|
||||
ctx->unlock();
|
||||
return true;
|
||||
@ -870,7 +871,12 @@ bool Context::checkRoutedDesign() const
|
||||
}
|
||||
|
||||
auto src_wire = ctx->getNetinfoSourceWire(net_info);
|
||||
log_assert(src_wire != WireId());
|
||||
if (src_wire == WireId()) {
|
||||
log_assert(net_info->driver.cell == nullptr);
|
||||
if (ctx->debug)
|
||||
log(" undriven and unrouted\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (net_info->wires.count(src_wire) == 0) {
|
||||
if (ctx->debug)
|
||||
|
@ -485,11 +485,11 @@ void assign_budget(Context *ctx, bool quiet)
|
||||
for (auto &user : net.second->users) {
|
||||
// Post-update check
|
||||
if (!ctx->auto_freq && user.budget < 0)
|
||||
log_warning("port %s.%s, connected to net '%s', has negative "
|
||||
log_info("port %s.%s, connected to net '%s', has negative "
|
||||
"timing budget of %fns\n",
|
||||
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
|
||||
ctx->getDelayNS(user.budget));
|
||||
else if (ctx->verbose)
|
||||
else if (ctx->debug)
|
||||
log_info("port %s.%s, connected to net '%s', has "
|
||||
"timing budget of %fns\n",
|
||||
user.cell->name.c_str(ctx), user.port.c_str(ctx), net.first.c_str(ctx),
|
||||
@ -513,7 +513,7 @@ void assign_budget(Context *ctx, bool quiet)
|
||||
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||
}
|
||||
|
||||
void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path)
|
||||
void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure)
|
||||
{
|
||||
auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
|
||||
std::string value;
|
||||
@ -593,7 +593,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
||||
|
||||
if (print_path) {
|
||||
auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
|
||||
delay_t total = 0;
|
||||
delay_t total = 0, logic_total = 0, route_total = 0;
|
||||
auto &front = crit_path.front();
|
||||
auto &front_port = front->cell->ports.at(front->port);
|
||||
auto &front_driver = front_port.net->driver;
|
||||
@ -608,6 +608,9 @@ 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();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -627,10 +630,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
||||
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
|
||||
}
|
||||
total += comb_delay.maxDelay();
|
||||
logic_total += comb_delay.maxDelay();
|
||||
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
|
||||
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
|
||||
auto net_delay = ctx->getNetinfoRouteDelay(net, *sink);
|
||||
total += net_delay;
|
||||
route_total += net_delay;
|
||||
auto driver_loc = ctx->getBelLocation(driver_cell->bel);
|
||||
auto sink_loc = ctx->getBelLocation(sink_cell->bel);
|
||||
log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay),
|
||||
@ -658,6 +663,17 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
||||
}
|
||||
last_port = sink->port;
|
||||
}
|
||||
int clockCount = 0;
|
||||
auto sinkClass = ctx->getPortTimingClass(crit_path.back()->cell, crit_path.back()->port, clockCount);
|
||||
if (sinkClass == TMG_REGISTER_INPUT && clockCount > 0) {
|
||||
auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back()->cell, crit_path.back()->port, 0);
|
||||
delay_t setup = sinkClockInfo.setup.maxDelay();
|
||||
total += setup;
|
||||
logic_total += setup;
|
||||
log_info("%4.1f %4.1f Setup %s.%s\n", ctx->getDelayNS(setup), ctx->getDelayNS(total),
|
||||
crit_path.back()->cell->name.c_str(ctx), crit_path.back()->port.c_str(ctx));
|
||||
}
|
||||
log_info("%.1f ns logic, %.1f ns routing\n", ctx->getDelayNS(logic_total), ctx->getDelayNS(route_total));
|
||||
};
|
||||
|
||||
for (auto &clock : clock_reports) {
|
||||
@ -689,15 +705,17 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
||||
for (auto &clock : clock_reports) {
|
||||
const auto &clock_name = clock.first.str(ctx);
|
||||
const int width = max_width - clock_name.size();
|
||||
if (ctx->nets.at(clock.first)->clkconstr) {
|
||||
float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
|
||||
float target = ctx->target_freq / 1e6;
|
||||
if (ctx->nets.at(clock.first)->clkconstr)
|
||||
target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
|
||||
|
||||
bool passed = target < clock_fmax[clock.first];
|
||||
if (!warn_on_failure || passed)
|
||||
log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
||||
clock_name.c_str(), clock_fmax[clock.first],
|
||||
(target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target);
|
||||
} else {
|
||||
log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(),
|
||||
clock_fmax[clock.first]);
|
||||
}
|
||||
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
||||
else
|
||||
log_nonfatal_error("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
||||
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
||||
}
|
||||
for (auto &eclock : empty_clocks) {
|
||||
if (eclock != ctx->id("$async$"))
|
||||
|
@ -29,7 +29,8 @@ void assign_budget(Context *ctx, bool quiet = false);
|
||||
|
||||
// Perform timing analysis and print out the fmax, and optionally the
|
||||
// critical path
|
||||
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false);
|
||||
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
|
||||
bool warn_on_failure = false);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
There are three types of constraints available for end users of nextpnr.
|
||||
|
||||
## Architecture-specific IO Cconstraints
|
||||
## Architecture-specific IO Constraints
|
||||
|
||||
Architectures may provide support for their native (or any other) IO constraint format.
|
||||
The iCE40 architecture supports PCF constraints thus:
|
||||
|
182
ecp5/arch.cc
182
ecp5/arch.cc
@ -96,7 +96,7 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
speed_grade = &(chip_info->speed_grades[args.speed]);
|
||||
if (!package_info)
|
||||
log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str());
|
||||
|
||||
@ -400,7 +400,7 @@ BelId Arch::getBelByLocation(Loc loc) const
|
||||
|
||||
delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
||||
{
|
||||
return 170 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y));
|
||||
return (240 - 20 * args.speed) * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y));
|
||||
}
|
||||
|
||||
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
|
||||
@ -409,7 +409,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
|
||||
auto driver_loc = getBelLocation(driver.cell->bel);
|
||||
auto sink_loc = getBelLocation(sink.cell->bel);
|
||||
|
||||
return 170 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y));
|
||||
return (240 - 20 * args.speed) * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y));
|
||||
}
|
||||
|
||||
bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
|
||||
@ -422,7 +422,34 @@ bool Arch::route()
|
||||
{
|
||||
route_ecp5_globals(getCtx());
|
||||
assign_budget(getCtx(), true);
|
||||
return router1(getCtx(), Router1Cfg(getCtx()));
|
||||
|
||||
bool result = router1(getCtx(), Router1Cfg(getCtx()));
|
||||
#if 0
|
||||
std::vector<std::pair<WireId, int>> fanout_vector;
|
||||
std::copy(wire_fanout.begin(), wire_fanout.end(), std::back_inserter(fanout_vector));
|
||||
std::sort(fanout_vector.begin(), fanout_vector.end(), [](const std::pair<WireId, int> &a, const std::pair<WireId, int> &b) {
|
||||
return a.second > b.second;
|
||||
});
|
||||
for (size_t i = 0; i < std::min(size_t(20), fanout_vector.size()); i++)
|
||||
log_info(" fanout %s = %d\n", getWireName(fanout_vector[i].first).c_str(this), fanout_vector[i].second);
|
||||
log_break();
|
||||
PipId slowest_pip;
|
||||
delay_t slowest_pipdelay = 0;
|
||||
for (auto pip : pip_to_net) {
|
||||
if (pip.second) {
|
||||
delay_t dly = getPipDelay(pip.first).maxDelay();
|
||||
if (dly > slowest_pipdelay) {
|
||||
slowest_pip = pip.first;
|
||||
slowest_pipdelay = dly;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_info(" slowest pip %s = %.02f ns\n", getPipName(slowest_pip).c_str(this), getDelayNS(slowest_pipdelay));
|
||||
log_info(" fanout %d\n", wire_fanout[getPipSrcWire(slowest_pip)]);
|
||||
log_info(" base %d adder %d\n", speed_grade->pip_classes[locInfo(slowest_pip)->pip_data[slowest_pip.index].timing_class].max_base_delay,
|
||||
speed_grade->pip_classes[locInfo(slowest_pip)->pip_data[slowest_pip.index].timing_class].max_fanout_adder);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@ -482,94 +509,75 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const
|
||||
{
|
||||
for (int i = 0; i < speed_grade->num_cell_timings; i++) {
|
||||
const auto &tc = speed_grade->cell_timings[i];
|
||||
if (tc.cell_type == tctype.index) {
|
||||
for (int j = 0; j < tc.num_prop_delays; j++) {
|
||||
const auto &dly = tc.prop_delays[j];
|
||||
if (dly.from_port == from.index && dly.to_port == to.index) {
|
||||
delay.max_delay = dly.max_delay;
|
||||
delay.min_delay = dly.min_delay;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT_FALSE("failed to find timing cell in db");
|
||||
}
|
||||
|
||||
void Arch::getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdString port, DelayInfo &setup,
|
||||
DelayInfo &hold) const
|
||||
{
|
||||
for (int i = 0; i < speed_grade->num_cell_timings; i++) {
|
||||
const auto &tc = speed_grade->cell_timings[i];
|
||||
if (tc.cell_type == tctype.index) {
|
||||
for (int j = 0; j < tc.num_setup_holds; j++) {
|
||||
const auto &sh = tc.setup_holds[j];
|
||||
if (sh.clock_port == clock.index && sh.sig_port == port.index) {
|
||||
setup.max_delay = sh.max_setup;
|
||||
setup.min_delay = sh.min_setup;
|
||||
hold.max_delay = sh.max_hold;
|
||||
hold.min_delay = sh.min_hold;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT_FALSE("failed to find timing cell in db");
|
||||
}
|
||||
|
||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
||||
{
|
||||
|
||||
// Data for -8 grade
|
||||
if (cell->type == id_TRELLIS_SLICE) {
|
||||
bool has_carry = str_or_default(cell->params, id("MODE"), "LOGIC") == "CCU2";
|
||||
if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0) {
|
||||
if (toPort == id_F0) {
|
||||
delay.delay = 180;
|
||||
return true;
|
||||
} else if (has_carry && toPort == id_F1) {
|
||||
delay.delay = 500;
|
||||
return true;
|
||||
} else if (has_carry && toPort == id_FCO) {
|
||||
delay.delay = 355;
|
||||
return true;
|
||||
} else if (toPort == id_OFX0) {
|
||||
delay.delay = 306;
|
||||
return true;
|
||||
}
|
||||
if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0 || fromPort == id_A1 ||
|
||||
fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1 || fromPort == id_M0 || fromPort == id_M1 ||
|
||||
fromPort == id_FXA || fromPort == id_FXB || fromPort == id_FCI) {
|
||||
return getDelayFromTimingDatabase(has_carry ? id_SCCU2C : id_SLOGICB, fromPort, toPort, delay);
|
||||
}
|
||||
|
||||
if (fromPort == id_A1 || fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1) {
|
||||
if (toPort == id_F1) {
|
||||
delay.delay = 180;
|
||||
return true;
|
||||
} else if (has_carry && toPort == id_FCO) {
|
||||
delay.delay = 355;
|
||||
return true;
|
||||
} else if (toPort == id_OFX0) {
|
||||
delay.delay = 306;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_carry && fromPort == id_FCI) {
|
||||
if (toPort == id_F0) {
|
||||
delay.delay = 328;
|
||||
return true;
|
||||
} else if (toPort == id_F1) {
|
||||
delay.delay = 349;
|
||||
return true;
|
||||
} else if (toPort == id_FCO) {
|
||||
delay.delay = 56;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromPort == id_CLK && (toPort == id_Q0 || toPort == id_Q1)) {
|
||||
delay.delay = 395;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fromPort == id_M0 && toPort == id_OFX0) {
|
||||
delay.delay = 193;
|
||||
return true;
|
||||
}
|
||||
#if 0 // FIXME
|
||||
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
|
||||
delay.delay = 717;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
|
||||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
|
||||
(fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) ||
|
||||
(fromPort == id_D0 && toPort == id_WADO0) || (fromPort == id_D1 && toPort == id_WDO2)) {
|
||||
delay.delay = 0;
|
||||
delay.min_delay = 0;
|
||||
delay.max_delay = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (cell->type == id_DCCA) {
|
||||
if (fromPort == id_CLKI && toPort == id_CLKO) {
|
||||
delay.delay = 0;
|
||||
delay.min_delay = 0;
|
||||
delay.max_delay = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
if (fromPort == id_CLKA) {
|
||||
if (toPort.str(this).substr(0, 3) == "DOA") {
|
||||
delay.delay = 4260;
|
||||
return true;
|
||||
}
|
||||
} else if (fromPort == id_CLKB) {
|
||||
if (toPort.str(this).substr(0, 3) == "DOB") {
|
||||
delay.delay = 4280;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
@ -669,9 +677,9 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
|
||||
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||
{
|
||||
TimingClockingInfo info;
|
||||
info.setup.delay = 0;
|
||||
info.hold.delay = 0;
|
||||
info.clockToQ.delay = 0;
|
||||
info.setup = getDelayFromNS(0);
|
||||
info.hold = getDelayFromNS(0);
|
||||
info.clockToQ = getDelayFromNS(0);
|
||||
if (cell->type == id_TRELLIS_SLICE) {
|
||||
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
||||
|
||||
@ -679,18 +687,18 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
|
||||
port == id_WAD3 || port == id_WRE) {
|
||||
info.edge = RISING_EDGE;
|
||||
info.clock_port = id_WCK;
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
getSetupHoldFromTimingDatabase(id_SDPRAME, id_WCK, port, info.setup, info.hold);
|
||||
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
||||
(sd1 == 1 && port == id_M1)) {
|
||||
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||
info.clock_port = id_CLK;
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
getSetupHoldFromTimingDatabase(id_SLOGICB, id_CLK, port, info.setup, info.hold);
|
||||
|
||||
} else {
|
||||
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||
info.clock_port = id_CLK;
|
||||
info.clockToQ.delay = 395;
|
||||
bool is_path = getDelayFromTimingDatabase(id_SLOGICB, id_CLK, port, info.clockToQ);
|
||||
NPNR_ASSERT(is_path);
|
||||
}
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
std::string port_name = port.str(this);
|
||||
@ -711,10 +719,12 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
|
||||
? FALLING_EDGE
|
||||
: RISING_EDGE;
|
||||
if (cell->ports.at(port).type == PORT_OUT) {
|
||||
info.clockToQ.delay = 4280;
|
||||
bool is_path = getDelayFromTimingDatabase(id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG, info.clock_port, port,
|
||||
info.clockToQ);
|
||||
NPNR_ASSERT(is_path);
|
||||
} else {
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
getSetupHoldFromTimingDatabase(id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG, info.clock_port, port, info.setup,
|
||||
info.hold);
|
||||
}
|
||||
} else if (cell->type == id_DCUA) {
|
||||
std::string prefix = port.str(this).substr(0, 9);
|
||||
@ -728,10 +738,10 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
|
||||
else if (prefix == "CH1_FF_RX")
|
||||
info.clock_port = id_CH1_FF_RXI_CLK;
|
||||
if (cell->ports.at(port).type == PORT_OUT) {
|
||||
info.clockToQ.delay = 660;
|
||||
info.clockToQ = getDelayFromNS(0.7);
|
||||
} else {
|
||||
info.setup.delay = 1000;
|
||||
info.hold.delay = 0;
|
||||
info.setup = getDelayFromNS(1);
|
||||
info.hold = getDelayFromNS(0);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
|
76
ecp5/arch.h
76
ecp5/arch.h
@ -71,7 +71,7 @@ NPNR_PACKED_STRUCT(struct BelPortPOD {
|
||||
NPNR_PACKED_STRUCT(struct PipInfoPOD {
|
||||
LocationPOD rel_src_loc, rel_dst_loc;
|
||||
int32_t src_idx, dst_idx;
|
||||
int32_t delay;
|
||||
int32_t timing_class;
|
||||
int16_t tile_type;
|
||||
int8_t pip_type;
|
||||
int8_t padding_0;
|
||||
@ -151,6 +151,44 @@ NPNR_PACKED_STRUCT(struct GlobalInfoPOD {
|
||||
int16_t spine_col;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct CellPropDelayPOD {
|
||||
int32_t from_port;
|
||||
int32_t to_port;
|
||||
int32_t min_delay;
|
||||
int32_t max_delay;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct CellSetupHoldPOD {
|
||||
int32_t sig_port;
|
||||
int32_t clock_port;
|
||||
int32_t min_setup;
|
||||
int32_t max_setup;
|
||||
int32_t min_hold;
|
||||
int32_t max_hold;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct CellTimingPOD {
|
||||
int32_t cell_type;
|
||||
int32_t num_prop_delays;
|
||||
int32_t num_setup_holds;
|
||||
RelPtr<CellPropDelayPOD> prop_delays;
|
||||
RelPtr<CellSetupHoldPOD> setup_holds;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct PipDelayPOD {
|
||||
int32_t min_base_delay;
|
||||
int32_t max_base_delay;
|
||||
int32_t min_fanout_adder;
|
||||
int32_t max_fanout_adder;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct SpeedGradePOD {
|
||||
int32_t num_cell_timings;
|
||||
int32_t num_pip_classes;
|
||||
RelPtr<CellTimingPOD> cell_timings;
|
||||
RelPtr<PipDelayPOD> pip_classes;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||
int32_t width, height;
|
||||
int32_t num_tiles;
|
||||
@ -163,6 +201,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
|
||||
RelPtr<PackageInfoPOD> package_info;
|
||||
RelPtr<PIOInfoPOD> pio_info;
|
||||
RelPtr<TileInfoPOD> tile_info;
|
||||
RelPtr<SpeedGradePOD> speed_grades;
|
||||
});
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
@ -400,13 +439,20 @@ struct ArchArgs
|
||||
LFE5UM5G_85F,
|
||||
} type = NONE;
|
||||
std::string package;
|
||||
int speed = 6;
|
||||
enum SpeedGrade
|
||||
{
|
||||
SPEED_6 = 0,
|
||||
SPEED_7,
|
||||
SPEED_8,
|
||||
SPEED_8_5G,
|
||||
} speed = SPEED_6;
|
||||
};
|
||||
|
||||
struct Arch : BaseCtx
|
||||
{
|
||||
const ChipInfoPOD *chip_info;
|
||||
const PackageInfoPOD *package_info;
|
||||
const SpeedGradePOD *speed_grade;
|
||||
|
||||
mutable std::unordered_map<IdString, BelId> bel_by_name;
|
||||
mutable std::unordered_map<IdString, WireId> wire_by_name;
|
||||
@ -415,6 +461,7 @@ struct Arch : BaseCtx
|
||||
std::vector<CellInfo *> bel_to_cell;
|
||||
std::unordered_map<WireId, NetInfo *> wire_to_net;
|
||||
std::unordered_map<PipId, NetInfo *> pip_to_net;
|
||||
std::unordered_map<WireId, int> wire_fanout;
|
||||
|
||||
ArchArgs args;
|
||||
Arch(ArchArgs args);
|
||||
@ -597,6 +644,7 @@ struct Arch : BaseCtx
|
||||
|
||||
auto pip = it->second.pip;
|
||||
if (pip != PipId()) {
|
||||
wire_fanout[getPipSrcWire(pip)]--;
|
||||
pip_to_net[pip] = nullptr;
|
||||
}
|
||||
|
||||
@ -633,7 +681,8 @@ struct Arch : BaseCtx
|
||||
DelayInfo getWireDelay(WireId wire) const
|
||||
{
|
||||
DelayInfo delay;
|
||||
delay.delay = 0;
|
||||
delay.min_delay = 0;
|
||||
delay.max_delay = 0;
|
||||
return delay;
|
||||
}
|
||||
|
||||
@ -686,6 +735,7 @@ struct Arch : BaseCtx
|
||||
NPNR_ASSERT(pip_to_net[pip] == nullptr);
|
||||
|
||||
pip_to_net[pip] = net;
|
||||
wire_fanout[getPipSrcWire(pip)]++;
|
||||
|
||||
WireId dst;
|
||||
dst.index = locInfo(pip)->pip_data[pip.index].dst_idx;
|
||||
@ -700,6 +750,7 @@ struct Arch : BaseCtx
|
||||
{
|
||||
NPNR_ASSERT(pip != PipId());
|
||||
NPNR_ASSERT(pip_to_net[pip] != nullptr);
|
||||
wire_fanout[getPipSrcWire(pip)]--;
|
||||
|
||||
WireId dst;
|
||||
dst.index = locInfo(pip)->pip_data[pip.index].dst_idx;
|
||||
@ -772,7 +823,17 @@ struct Arch : BaseCtx
|
||||
{
|
||||
DelayInfo delay;
|
||||
NPNR_ASSERT(pip != PipId());
|
||||
delay.delay = locInfo(pip)->pip_data[pip.index].delay;
|
||||
int fanout = 0;
|
||||
auto fnd_fanout = wire_fanout.find(getPipSrcWire(pip));
|
||||
if (fnd_fanout != wire_fanout.end())
|
||||
fanout = fnd_fanout->second;
|
||||
NPNR_ASSERT(locInfo(pip)->pip_data[pip.index].timing_class < speed_grade->num_pip_classes);
|
||||
delay.min_delay =
|
||||
speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].min_base_delay +
|
||||
fanout * speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].min_fanout_adder;
|
||||
delay.max_delay =
|
||||
speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].max_base_delay +
|
||||
fanout * speed_grade->pip_classes[locInfo(pip)->pip_data[pip.index].timing_class].max_fanout_adder;
|
||||
return delay;
|
||||
}
|
||||
|
||||
@ -862,7 +923,8 @@ struct Arch : BaseCtx
|
||||
DelayInfo getDelayFromNS(float ns) const
|
||||
{
|
||||
DelayInfo del;
|
||||
del.delay = delay_t(ns * 1000);
|
||||
del.min_delay = delay_t(ns * 1000);
|
||||
del.max_delay = delay_t(ns * 1000);
|
||||
return del;
|
||||
}
|
||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
||||
@ -895,6 +957,10 @@ struct Arch : BaseCtx
|
||||
// Return true if a port is a net
|
||||
bool isGlobalNet(const NetInfo *net) const;
|
||||
|
||||
bool getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const;
|
||||
void getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdString port, DelayInfo &setup,
|
||||
DelayInfo &hold) const;
|
||||
|
||||
// -------------------------------------------------
|
||||
// Placement validity checks
|
||||
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
||||
|
@ -75,6 +75,8 @@ bool Arch::isBelLocationValid(BelId bel) const
|
||||
bel_cells.push_back(cell_other);
|
||||
}
|
||||
}
|
||||
if (getBoundBelCell(bel) != nullptr && getBoundBelCell(bel)->sliceInfo.has_l6mux && ((bel_loc.z % 2) == 1))
|
||||
return false;
|
||||
return slicesCompatible(bel_cells);
|
||||
} else {
|
||||
CellInfo *cell = getBoundBelCell(bel);
|
||||
@ -92,6 +94,10 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
|
||||
|
||||
std::vector<const CellInfo *> bel_cells;
|
||||
Loc bel_loc = getBelLocation(bel);
|
||||
|
||||
if (cell->sliceInfo.has_l6mux && ((bel_loc.z % 2) == 1))
|
||||
return false;
|
||||
|
||||
for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
|
||||
CellInfo *cell_other = getBoundBelCell(bel_other);
|
||||
if (cell_other != nullptr && bel_other != bel) {
|
||||
|
@ -44,14 +44,36 @@ template <> struct string_converter<WireId>
|
||||
{
|
||||
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, WireId id) { return ctx->getWireName(id).str(ctx); }
|
||||
std::string to_str(Context *ctx, WireId id)
|
||||
{
|
||||
if (id == WireId())
|
||||
throw bad_wrap();
|
||||
return ctx->getWireName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct string_converter<const WireId>
|
||||
{
|
||||
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, WireId id)
|
||||
{
|
||||
if (id == WireId())
|
||||
throw bad_wrap();
|
||||
return ctx->getWireName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct string_converter<PipId>
|
||||
{
|
||||
PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, PipId id) { return ctx->getPipName(id).str(ctx); }
|
||||
std::string to_str(Context *ctx, PipId id)
|
||||
{
|
||||
if (id == PipId())
|
||||
throw bad_wrap();
|
||||
return ctx->getPipName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace PythonConversion
|
||||
|
@ -30,21 +30,22 @@ typedef int delay_t;
|
||||
|
||||
struct DelayInfo
|
||||
{
|
||||
delay_t delay = 0;
|
||||
delay_t min_delay = 0, max_delay = 0;
|
||||
|
||||
delay_t minRaiseDelay() const { return delay; }
|
||||
delay_t maxRaiseDelay() const { return delay; }
|
||||
delay_t minRaiseDelay() const { return min_delay; }
|
||||
delay_t maxRaiseDelay() const { return max_delay; }
|
||||
|
||||
delay_t minFallDelay() const { return delay; }
|
||||
delay_t maxFallDelay() const { return delay; }
|
||||
delay_t minFallDelay() const { return min_delay; }
|
||||
delay_t maxFallDelay() const { return max_delay; }
|
||||
|
||||
delay_t minDelay() const { return delay; }
|
||||
delay_t maxDelay() const { return delay; }
|
||||
delay_t minDelay() const { return min_delay; }
|
||||
delay_t maxDelay() const { return max_delay; }
|
||||
|
||||
DelayInfo operator+(const DelayInfo &other) const
|
||||
{
|
||||
DelayInfo ret;
|
||||
ret.delay = this->delay + other.delay;
|
||||
ret.min_delay = this->min_delay + other.min_delay;
|
||||
ret.max_delay = this->max_delay + other.max_delay;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
@ -157,6 +158,7 @@ struct ArchCellInfo
|
||||
struct
|
||||
{
|
||||
bool using_dff;
|
||||
bool has_l6mux;
|
||||
IdString clk_sig, lsr_sig, clkmux, lsrmux, srmode;
|
||||
} sliceInfo;
|
||||
};
|
||||
|
@ -407,8 +407,8 @@ std::vector<std::string> get_pll_tiles(Context *ctx, BelId bel)
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "PLL0_LR"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, pll1_lr));
|
||||
} else if (name == "EHXPLL_UR") {
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x - 1, "PLL0_UR"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x - 1, "PLL1_UR"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + 1, "PLL0_UR"));
|
||||
tiles.push_back(ctx->getTileByTypeAndLocation(loc.y + 1, loc.x + 1, "PLL1_UR"));
|
||||
} else {
|
||||
NPNR_ASSERT_FALSE_STR("bad PLL loc " + name);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type
|
||||
|
||||
inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("CCU2C"); }
|
||||
|
||||
inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_LC"); }
|
||||
inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_SLICE"); }
|
||||
|
||||
inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); }
|
||||
|
||||
|
@ -811,7 +811,6 @@ X(INTLOCK)
|
||||
X(REFCLK)
|
||||
X(CLKINTFB)
|
||||
|
||||
|
||||
X(EXTREFB)
|
||||
X(REFCLKP)
|
||||
X(REFCLKN)
|
||||
@ -1117,3 +1116,29 @@ X(SEL1)
|
||||
X(SEL0)
|
||||
X(CDIV1)
|
||||
X(CDIVX)
|
||||
|
||||
X(DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG)
|
||||
X(DP16KD_REGMODE_A_NOREG_REGMODE_B_OUTREG)
|
||||
X(DP16KD_REGMODE_A_OUTREG_REGMODE_B_NOREG)
|
||||
X(DP16KD_REGMODE_A_OUTREG_REGMODE_B_OUTREG)
|
||||
X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_NORMAL)
|
||||
X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_READBEFOREWRITE)
|
||||
X(DP16KD_WRITEMODE_A_NORMAL_WRITEMODE_B_WRITETHROUGH)
|
||||
X(PIO_IOTYPE_LVCMOS12)
|
||||
X(PIO_IOTYPE_LVCMOS15)
|
||||
X(PIO_IOTYPE_LVCMOS18)
|
||||
X(PIO_IOTYPE_LVCMOS25)
|
||||
X(PIO_IOTYPE_LVCMOS33)
|
||||
X(PIO_IOTYPE_LVDS)
|
||||
X(PIO_IOTYPE_SSTL15_I)
|
||||
X(PIO_IOTYPE_SSTL15_II)
|
||||
X(PIO_IOTYPE_SSTL18_I)
|
||||
X(PIO_IOTYPE_SSTL18_II)
|
||||
X(SCCU2C)
|
||||
X(SDPRAME)
|
||||
X(SLOGICB)
|
||||
X(SRAMWB)
|
||||
X(PAD)
|
||||
X(PADDI)
|
||||
X(PADDO)
|
||||
X(PADDT)
|
||||
|
@ -18,11 +18,13 @@ file(MAKE_DIRECTORY ecp5/chipdbs/)
|
||||
add_library(ecp5_chipdb OBJECT ecp5/chipdbs/)
|
||||
target_compile_definitions(ecp5_chipdb PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family})
|
||||
target_include_directories(ecp5_chipdb PRIVATE ${family}/)
|
||||
|
||||
if (WIN32)
|
||||
set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${TRELLIS_ROOT}/libtrellis\;${TRELLIS_ROOT}/util/common\"")
|
||||
set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${TRELLIS_ROOT}/libtrellis\;${TRELLIS_ROOT}/util/common\;${TRELLIS_ROOT}/timing/util\"")
|
||||
else()
|
||||
set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common")
|
||||
set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common:${TRELLIS_ROOT}/timing/util")
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
target_sources(ecp5_chipdb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resource/embed.cc)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC)
|
||||
|
28
ecp5/main.cc
28
ecp5/main.cc
@ -59,6 +59,8 @@ po::options_description ECP5CommandHandler::getArchOptions()
|
||||
specific.add_options()("um5g-85k", "set device type to LFE5UM5G-85F");
|
||||
|
||||
specific.add_options()("package", po::value<std::string>(), "select device package (defaults to CABGA381)");
|
||||
specific.add_options()("speed", po::value<int>(), "select device speedgrade (6, 7 or 8)");
|
||||
|
||||
specific.add_options()("basecfg", po::value<std::string>(), "base chip configuration in Trellis text format");
|
||||
specific.add_options()("textcfg", po::value<std::string>(), "textual configuration in Trellis format to write");
|
||||
|
||||
@ -111,7 +113,31 @@ std::unique_ptr<Context> ECP5CommandHandler::createContext()
|
||||
chipArgs.package = vm["package"].as<std::string>();
|
||||
else
|
||||
chipArgs.package = "CABGA381";
|
||||
chipArgs.speed = 6;
|
||||
if (chipArgs.type == ArchArgs::LFE5UM5G_25F || chipArgs.type == ArchArgs::LFE5UM5G_45F ||
|
||||
chipArgs.type == ArchArgs::LFE5UM5G_85F) {
|
||||
if (vm.count("speed") && vm["speed"].as<int>() != 8)
|
||||
log_error("Only speed grade 8 is available for 5G parts\n");
|
||||
chipArgs.speed = ArchArgs::SPEED_8_5G;
|
||||
} else {
|
||||
if (vm.count("speed")) {
|
||||
int speed = vm["speed"].as<int>();
|
||||
switch (speed) {
|
||||
case 6:
|
||||
chipArgs.speed = ArchArgs::SPEED_6;
|
||||
break;
|
||||
case 7:
|
||||
chipArgs.speed = ArchArgs::SPEED_7;
|
||||
break;
|
||||
case 8:
|
||||
chipArgs.speed = ArchArgs::SPEED_8;
|
||||
break;
|
||||
default:
|
||||
log_error("Unsupported speed grade '%d'\n", speed);
|
||||
}
|
||||
} else {
|
||||
chipArgs.speed = ArchArgs::SPEED_6;
|
||||
}
|
||||
}
|
||||
|
||||
return std::unique_ptr<Context>(new Context(chipArgs));
|
||||
}
|
||||
|
161
ecp5/pack.cc
161
ecp5/pack.cc
@ -357,9 +357,9 @@ class Ecp5Packer
|
||||
}
|
||||
|
||||
// Pass to pack LUT5s into a newly created slice
|
||||
void pack_lut5s()
|
||||
void pack_lut5xs()
|
||||
{
|
||||
log_info("Packing LUT5s...\n");
|
||||
log_info("Packing LUT5-7s...\n");
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_pfumx(ctx, ci)) {
|
||||
@ -377,6 +377,8 @@ class Ecp5Packer
|
||||
log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
|
||||
if (lut1 == nullptr)
|
||||
log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
|
||||
if (ctx->verbose)
|
||||
log_info(" mux '%s' forms part of a LUT5\n", cell.first.c_str(ctx));
|
||||
replace_port(lut0, ctx->id("A"), packed.get(), ctx->id("A0"));
|
||||
replace_port(lut0, ctx->id("B"), packed.get(), ctx->id("B0"));
|
||||
replace_port(lut0, ctx->id("C"), packed.get(), ctx->id("C0"));
|
||||
@ -412,8 +414,157 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
// Pack LUT6s
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_l6mux(ctx, ci)) {
|
||||
NetInfo *ofx0_0 = ci->ports.at(ctx->id("D0")).net;
|
||||
if (ofx0_0 == nullptr)
|
||||
log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx));
|
||||
NetInfo *ofx0_1 = ci->ports.at(ctx->id("D1")).net;
|
||||
if (ofx0_1 == nullptr)
|
||||
log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx));
|
||||
CellInfo *slice0 = net_driven_by(ctx, ofx0_0, is_lc, ctx->id("OFX0"));
|
||||
CellInfo *slice1 = net_driven_by(ctx, ofx0_1, is_lc, ctx->id("OFX0"));
|
||||
if (slice0 == nullptr) {
|
||||
if (!net_driven_by(ctx, ofx0_0, is_l6mux, ctx->id("Z")) &&
|
||||
!net_driven_by(ctx, ofx0_0, is_lc, ctx->id("OFX1")))
|
||||
log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX0 but not a LUT7 mux "
|
||||
"('%s.%s')\n",
|
||||
ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx),
|
||||
ofx0_0->driver.port.c_str(ctx));
|
||||
continue;
|
||||
}
|
||||
if (slice1 == nullptr) {
|
||||
if (!net_driven_by(ctx, ofx0_1, is_l6mux, ctx->id("Z")) &&
|
||||
!net_driven_by(ctx, ofx0_1, is_lc, ctx->id("OFX1")))
|
||||
log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX0 but not a LUT7 mux "
|
||||
"('%s.%s')\n",
|
||||
ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx),
|
||||
ofx0_0->driver.port.c_str(ctx));
|
||||
continue;
|
||||
}
|
||||
if (ctx->verbose)
|
||||
log_info(" mux '%s' forms part of a LUT6\n", cell.first.c_str(ctx));
|
||||
replace_port(ci, ctx->id("D0"), slice1, id_FXA);
|
||||
replace_port(ci, ctx->id("D1"), slice1, id_FXB);
|
||||
replace_port(ci, ctx->id("SD"), slice1, id_M1);
|
||||
replace_port(ci, ctx->id("Z"), slice1, id_OFX1);
|
||||
slice0->constr_z = 1;
|
||||
slice0->constr_x = 0;
|
||||
slice0->constr_y = 0;
|
||||
slice0->constr_parent = slice1;
|
||||
slice1->constr_z = 0;
|
||||
slice1->constr_abs_z = true;
|
||||
slice1->constr_children.push_back(slice0);
|
||||
|
||||
if (lutffPairs.find(ci->name) != lutffPairs.end()) {
|
||||
CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get();
|
||||
ff_to_slice(ctx, ff, slice1, 1, true);
|
||||
packed_cells.insert(ff->name);
|
||||
sliceUsage[slice1->name].ff1_used = true;
|
||||
lutffPairs.erase(ci->name);
|
||||
fflutPairs.erase(ff->name);
|
||||
}
|
||||
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
// Pack LUT7s
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_l6mux(ctx, ci)) {
|
||||
NetInfo *ofx1_0 = ci->ports.at(ctx->id("D0")).net;
|
||||
if (ofx1_0 == nullptr)
|
||||
log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx));
|
||||
NetInfo *ofx1_1 = ci->ports.at(ctx->id("D1")).net;
|
||||
if (ofx1_1 == nullptr)
|
||||
log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx));
|
||||
CellInfo *slice1 = net_driven_by(ctx, ofx1_0, is_lc, ctx->id("OFX1"));
|
||||
CellInfo *slice3 = net_driven_by(ctx, ofx1_1, is_lc, ctx->id("OFX1"));
|
||||
if (slice1 == nullptr)
|
||||
log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX ('%s.%s')\n",
|
||||
ci->name.c_str(ctx), ofx1_0->driver.cell->name.c_str(ctx),
|
||||
ofx1_0->driver.port.c_str(ctx));
|
||||
if (slice3 == nullptr)
|
||||
log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX ('%s.%s')\n",
|
||||
ci->name.c_str(ctx), ofx1_1->driver.cell->name.c_str(ctx),
|
||||
ofx1_1->driver.port.c_str(ctx));
|
||||
|
||||
NetInfo *fxa_0 = slice1->ports.at(id_FXA).net;
|
||||
if (fxa_0 == nullptr)
|
||||
log_error("SLICE '%s' has disconnected port 'FXA'\n", slice1->name.c_str(ctx));
|
||||
NetInfo *fxa_1 = slice3->ports.at(id_FXA).net;
|
||||
if (fxa_1 == nullptr)
|
||||
log_error("SLICE '%s' has disconnected port 'FXA'\n", slice3->name.c_str(ctx));
|
||||
|
||||
CellInfo *slice0 = net_driven_by(ctx, fxa_0, is_lc, ctx->id("OFX0"));
|
||||
CellInfo *slice2 = net_driven_by(ctx, fxa_1, is_lc, ctx->id("OFX0"));
|
||||
if (slice0 == nullptr)
|
||||
log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n",
|
||||
slice1->name.c_str(ctx), fxa_0->driver.cell->name.c_str(ctx),
|
||||
fxa_0->driver.port.c_str(ctx));
|
||||
if (slice2 == nullptr)
|
||||
log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n",
|
||||
slice3->name.c_str(ctx), fxa_1->driver.cell->name.c_str(ctx),
|
||||
fxa_1->driver.port.c_str(ctx));
|
||||
|
||||
replace_port(ci, ctx->id("D0"), slice2, id_FXA);
|
||||
replace_port(ci, ctx->id("D1"), slice2, id_FXB);
|
||||
replace_port(ci, ctx->id("SD"), slice2, id_M1);
|
||||
replace_port(ci, ctx->id("Z"), slice2, id_OFX1);
|
||||
|
||||
for (auto slice : {slice0, slice1, slice2, slice3}) {
|
||||
slice->constr_children.clear();
|
||||
slice->constr_abs_z = false;
|
||||
slice->constr_x = slice->UNCONSTR;
|
||||
slice->constr_y = slice->UNCONSTR;
|
||||
slice->constr_z = slice->UNCONSTR;
|
||||
slice->constr_parent = nullptr;
|
||||
}
|
||||
slice3->constr_children.clear();
|
||||
slice3->constr_abs_z = true;
|
||||
slice3->constr_z = 0;
|
||||
|
||||
slice2->constr_children.clear();
|
||||
slice2->constr_abs_z = true;
|
||||
slice2->constr_z = 1;
|
||||
slice2->constr_x = 0;
|
||||
slice2->constr_y = 0;
|
||||
slice2->constr_parent = slice3;
|
||||
slice3->constr_children.push_back(slice2);
|
||||
|
||||
slice1->constr_children.clear();
|
||||
slice1->constr_abs_z = true;
|
||||
slice1->constr_z = 2;
|
||||
slice1->constr_x = 0;
|
||||
slice1->constr_y = 0;
|
||||
slice1->constr_parent = slice3;
|
||||
slice3->constr_children.push_back(slice1);
|
||||
|
||||
slice0->constr_children.clear();
|
||||
slice0->constr_abs_z = true;
|
||||
slice0->constr_z = 3;
|
||||
slice0->constr_x = 0;
|
||||
slice0->constr_y = 0;
|
||||
slice0->constr_parent = slice3;
|
||||
slice3->constr_children.push_back(slice0);
|
||||
|
||||
if (lutffPairs.find(ci->name) != lutffPairs.end()) {
|
||||
CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get();
|
||||
ff_to_slice(ctx, ff, slice2, 1, true);
|
||||
packed_cells.insert(ff->name);
|
||||
sliceUsage[slice2->name].ff1_used = true;
|
||||
lutffPairs.erase(ci->name);
|
||||
fflutPairs.erase(ff->name);
|
||||
}
|
||||
|
||||
packed_cells.insert(ci->name);
|
||||
}
|
||||
}
|
||||
flush_cells();
|
||||
}
|
||||
// Create a feed in to the carry chain
|
||||
CellInfo *make_carry_feed_in(NetInfo *carry, PortRef chain_in)
|
||||
{
|
||||
@ -1183,7 +1334,7 @@ class Ecp5Packer
|
||||
pack_dram();
|
||||
pack_carries();
|
||||
find_lutff_pairs();
|
||||
pack_lut5s();
|
||||
pack_lut5xs();
|
||||
pair_luts();
|
||||
pack_lut_pairs();
|
||||
pack_remaining_luts();
|
||||
@ -1252,6 +1403,10 @@ void Arch::assignArchInfo()
|
||||
ci->sliceInfo.clkmux = id(str_or_default(ci->params, id_CLKMUX, "CLK"));
|
||||
ci->sliceInfo.lsrmux = id(str_or_default(ci->params, id_LSRMUX, "LSR"));
|
||||
ci->sliceInfo.srmode = id(str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE"));
|
||||
ci->sliceInfo.has_l6mux = false;
|
||||
if (ci->ports.count(id_FXA) && ci->ports[id_FXA].net != nullptr &&
|
||||
ci->ports[id_FXA].net->driver.port == id_OFX0)
|
||||
ci->sliceInfo.has_l6mux = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ std::unique_ptr<Context> ProjectHandler::createContext(pt::ptree &root)
|
||||
chipArgs.type = ArchArgs::LFE5U_85F;
|
||||
}
|
||||
chipArgs.package = root.get<std::string>("project.arch.package");
|
||||
chipArgs.speed = root.get<int>("project.arch.speed");
|
||||
chipArgs.speed = ArchArgs::SpeedGrade(root.get<int>("project.arch.speed"));
|
||||
|
||||
return std::unique_ptr<Context>(new Context(chipArgs));
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import pytrellis
|
||||
import database
|
||||
import argparse
|
||||
import json
|
||||
import pip_classes
|
||||
import timing_dbs
|
||||
from os import path
|
||||
|
||||
location_types = dict()
|
||||
@ -135,34 +137,81 @@ def process_loc_globals(chip):
|
||||
spine = (-1, -1)
|
||||
global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col, spine)
|
||||
|
||||
def get_wire_type(name):
|
||||
if "H00" in name or "V00" in name:
|
||||
return "X0"
|
||||
if "H01" in name or "V01" in name:
|
||||
return "X1"
|
||||
if "H02" in name or "V02" in name:
|
||||
return "X2"
|
||||
if "H06" in name or "V06" in name:
|
||||
return "X6"
|
||||
if "_SLICE" in name or "_EBR" in name:
|
||||
return "SLICE"
|
||||
return "LOCAL"
|
||||
|
||||
def get_pip_delay(wire_from, wire_to):
|
||||
# ECP5 timings WIP!!!
|
||||
type_from = get_wire_type(wire_from)
|
||||
type_to = get_wire_type(wire_to)
|
||||
if type_from == "X2" and type_to == "X2":
|
||||
return 170
|
||||
if type_from == "SLICE" or type_to == "SLICE":
|
||||
return 205
|
||||
if type_from in ("LOCAL", "X0") and type_to in ("X1", "X2", "X6"):
|
||||
return 90
|
||||
if type_from == "X6" or type_to == "X6":
|
||||
return 200
|
||||
if type_from in ("X1", "X2", "X6") and type_to in ("LOCAL", "X0"):
|
||||
return 90
|
||||
return 100
|
||||
speed_grade_names = ["6", "7", "8", "8_5G"]
|
||||
speed_grade_cells = {}
|
||||
speed_grade_pips = {}
|
||||
|
||||
pip_class_to_idx = {"default": 0}
|
||||
|
||||
timing_port_xform = {
|
||||
"RAD0": "D0",
|
||||
"RAD1": "B0",
|
||||
"RAD2": "C0",
|
||||
"RAD3": "A0",
|
||||
}
|
||||
|
||||
|
||||
def process_timing_data():
|
||||
for grade in speed_grade_names:
|
||||
with open(timing_dbs.cells_db_path("ECP5", grade)) as f:
|
||||
cell_data = json.load(f)
|
||||
cells = []
|
||||
for cell, cdata in sorted(cell_data.items()):
|
||||
celltype = constids[cell.replace(":", "_").replace("=", "_").replace(",", "_")]
|
||||
delays = []
|
||||
setupholds = []
|
||||
for entry in cdata:
|
||||
if entry["type"] == "Width":
|
||||
continue
|
||||
elif entry["type"] == "IOPath":
|
||||
from_pin = entry["from_pin"][1] if type(entry["from_pin"]) is list else entry["from_pin"]
|
||||
if from_pin in timing_port_xform:
|
||||
from_pin = timing_port_xform[from_pin]
|
||||
to_pin = entry["to_pin"]
|
||||
if to_pin in timing_port_xform:
|
||||
to_pin = timing_port_xform[to_pin]
|
||||
min_delay = min(entry["rising"][0], entry["falling"][0])
|
||||
max_delay = min(entry["rising"][2], entry["falling"][2])
|
||||
delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay))
|
||||
elif entry["type"] == "SetupHold":
|
||||
pin = constids[entry["pin"]]
|
||||
clock = constids[entry["clock"][1]]
|
||||
min_setup = entry["setup"][0]
|
||||
max_setup = entry["setup"][2]
|
||||
min_hold = entry["hold"][0]
|
||||
max_hold = entry["hold"][2]
|
||||
setupholds.append((pin, clock, min_setup, max_setup, min_hold, max_hold))
|
||||
else:
|
||||
assert False, entry["type"]
|
||||
cells.append((celltype, delays, setupholds))
|
||||
pip_class_delays = []
|
||||
for i in range(len(pip_class_to_idx)):
|
||||
pip_class_delays.append((50, 50, 0, 0))
|
||||
|
||||
with open(timing_dbs.interconnect_db_path("ECP5", grade)) as f:
|
||||
interconn_data = json.load(f)
|
||||
for pipclass, pipdata in sorted(interconn_data.items()):
|
||||
|
||||
min_delay = pipdata["delay"][0] * 1.1
|
||||
max_delay = pipdata["delay"][2] * 1.1
|
||||
min_fanout = pipdata["fanout"][0]
|
||||
max_fanout = pipdata["fanout"][2]
|
||||
if grade == "6":
|
||||
pip_class_to_idx[pipclass] = len(pip_class_delays)
|
||||
pip_class_delays.append((min_delay, max_delay, min_fanout, max_fanout))
|
||||
else:
|
||||
if pipclass in pip_class_to_idx:
|
||||
pip_class_delays[pip_class_to_idx[pipclass]] = (min_delay, max_delay, min_fanout, max_fanout)
|
||||
speed_grade_cells[grade] = cells
|
||||
speed_grade_pips[grade] = pip_class_delays
|
||||
|
||||
|
||||
def get_pip_class(wire_from, wire_to):
|
||||
class_name = pip_classes.get_pip_class(wire_from, wire_to)
|
||||
if class_name is None or class_name not in pip_class_to_idx:
|
||||
class_name = "default"
|
||||
return pip_class_to_idx[class_name]
|
||||
|
||||
|
||||
|
||||
@ -181,7 +230,7 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
loc = loc_with_type[arc_loctype]
|
||||
lt = ddrg.typeAtLocation[pytrellis.Location(loc[0] + rel.x, loc[1] + rel.y)]
|
||||
wire = ddrg.locationTypes[lt].wires[idx]
|
||||
return ddrg.to_str(wire.name)
|
||||
return "R{}C{}_{}".format(loc[1] + rel.y, loc[0] + rel.x, ddrg.to_str(wire.name))
|
||||
|
||||
bba = BinaryBlobAssembler()
|
||||
bba.pre('#include "nextpnr.h"')
|
||||
@ -202,7 +251,7 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
bba.u32(arc.sinkWire.id, "dst_idx")
|
||||
src_name = get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id)
|
||||
snk_name = get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id)
|
||||
bba.u32(get_pip_delay(src_name, snk_name), "delay") # TODO:delay
|
||||
bba.u32(get_pip_class(src_name, snk_name), "timing_class")
|
||||
bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type")
|
||||
cls = arc.cls
|
||||
if cls == 1 and "PCS" in snk_name or "DCU" in snk_name or "DCU" in src_name:
|
||||
@ -321,11 +370,53 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
bba.u16(bank, "bank")
|
||||
bba.u16(0, "padding")
|
||||
|
||||
|
||||
bba.l("tiletype_names", "RelPtr<char>")
|
||||
for tt, idx in sorted(tiletype_names.items(), key=lambda x: x[1]):
|
||||
bba.s(tt, "name")
|
||||
|
||||
for grade in speed_grade_names:
|
||||
for cell in speed_grade_cells[grade]:
|
||||
celltype, delays, setupholds = cell
|
||||
if len(delays) > 0:
|
||||
bba.l("cell_%d_delays_%s" % (celltype, grade))
|
||||
for delay in delays:
|
||||
from_pin, to_pin, min_delay, max_delay = delay
|
||||
bba.u32(from_pin, "from_pin")
|
||||
bba.u32(to_pin, "to_pin")
|
||||
bba.u32(min_delay, "min_delay")
|
||||
bba.u32(max_delay, "max_delay")
|
||||
if len(setupholds) > 0:
|
||||
bba.l("cell_%d_setupholds_%s" % (celltype, grade))
|
||||
for sh in setupholds:
|
||||
pin, clock, min_setup, max_setup, min_hold, max_hold = sh
|
||||
bba.u32(pin, "sig_port")
|
||||
bba.u32(clock, "clock_port")
|
||||
bba.u32(min_setup, "min_setup")
|
||||
bba.u32(max_setup, "max_setup")
|
||||
bba.u32(min_hold, "min_hold")
|
||||
bba.u32(max_hold, "max_hold")
|
||||
bba.l("cell_timing_data_%s" % grade)
|
||||
for cell in speed_grade_cells[grade]:
|
||||
celltype, delays, setupholds = cell
|
||||
bba.u32(celltype, "cell_type")
|
||||
bba.u32(len(delays), "num_delays")
|
||||
bba.u32(len(setupholds), "num_setup_hold")
|
||||
bba.r("cell_%d_delays_%s" % (celltype, grade) if len(delays) > 0 else None, "delays")
|
||||
bba.r("cell_%d_setupholds_%s" % (celltype, grade) if len(delays) > 0 else None, "setupholds")
|
||||
bba.l("pip_timing_data_%s" % grade)
|
||||
for pipclass in speed_grade_pips[grade]:
|
||||
min_delay, max_delay, min_fanout, max_fanout = pipclass
|
||||
bba.u32(min_delay, "min_delay")
|
||||
bba.u32(max_delay, "max_delay")
|
||||
bba.u32(min_fanout, "min_fanout")
|
||||
bba.u32(max_fanout, "max_fanout")
|
||||
bba.l("speed_grade_data")
|
||||
for grade in speed_grade_names:
|
||||
bba.u32(len(speed_grade_cells[grade]), "num_cell_timings")
|
||||
bba.u32(len(speed_grade_pips[grade]), "num_pip_classes")
|
||||
bba.r("cell_timing_data_%s" % grade, "cell_timings")
|
||||
bba.r("pip_timing_data_%s" % grade, "pip_classes")
|
||||
|
||||
bba.l("chip_info")
|
||||
bba.u32(max_col + 1, "width")
|
||||
bba.u32(max_row + 1, "height")
|
||||
@ -341,6 +432,7 @@ def write_database(dev_name, chip, ddrg, endianness):
|
||||
bba.r("package_data", "package_info")
|
||||
bba.r("pio_info", "pio_info")
|
||||
bba.r("tiles_info", "tile_info")
|
||||
bba.r("speed_grade_data", "speed_grades")
|
||||
|
||||
bba.pop()
|
||||
return bba
|
||||
@ -375,6 +467,7 @@ def main():
|
||||
ddrg = pytrellis.make_dedup_chipdb(chip)
|
||||
max_row = chip.get_max_row()
|
||||
max_col = chip.get_max_col()
|
||||
process_timing_data()
|
||||
process_pio_db(ddrg, args.device)
|
||||
process_loc_globals(chip)
|
||||
# print("{} unique location types".format(len(ddrg.locationTypes)))
|
||||
|
@ -44,7 +44,6 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, ArchArgs args,
|
||||
initBasenameResource();
|
||||
qRegisterMetaType<std::string>();
|
||||
|
||||
log_files.clear();
|
||||
log_streams.clear();
|
||||
|
||||
setObjectName("BaseMainWindow");
|
||||
|
@ -884,6 +884,13 @@ struct Arch : BaseCtx
|
||||
}
|
||||
NPNR_ASSERT_FALSE("Expected PLL pin to share an output with an SB_IO D_IN_{0,1}");
|
||||
}
|
||||
|
||||
int getDrivenGlobalNetwork(BelId bel) const
|
||||
{
|
||||
NPNR_ASSERT(getBelType(bel) == id_SB_GB);
|
||||
IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT));
|
||||
return std::stoi(std::string("") + glb_net.str(this).back());
|
||||
}
|
||||
};
|
||||
|
||||
void ice40DelayFuzzerMain(Context *ctx);
|
||||
|
@ -165,8 +165,7 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
|
||||
return true;
|
||||
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());
|
||||
int glb_id = getDrivenGlobalNetwork(bel);
|
||||
if (net->is_reset && net->is_enable)
|
||||
return false;
|
||||
else if (net->is_reset)
|
||||
|
@ -45,14 +45,36 @@ template <> struct string_converter<WireId>
|
||||
{
|
||||
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, WireId id) { return ctx->getWireName(id).str(ctx); }
|
||||
std::string to_str(Context *ctx, WireId id)
|
||||
{
|
||||
if (id == WireId())
|
||||
throw bad_wrap();
|
||||
return ctx->getWireName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct string_converter<const WireId>
|
||||
{
|
||||
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, WireId id)
|
||||
{
|
||||
if (id == WireId())
|
||||
throw bad_wrap();
|
||||
return ctx->getWireName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct string_converter<PipId>
|
||||
{
|
||||
PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); }
|
||||
|
||||
std::string to_str(Context *ctx, PipId id) { return ctx->getPipName(id).str(ctx); }
|
||||
std::string to_str(Context *ctx, PipId id)
|
||||
{
|
||||
if (id == PipId())
|
||||
throw bad_wrap();
|
||||
return ctx->getPipName(id).str(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace PythonConversion
|
||||
|
@ -587,10 +587,36 @@ static void promote_globals(Context *ctx)
|
||||
}
|
||||
}
|
||||
int prom_globals = 0, prom_resets = 0, prom_cens = 0, prom_logics = 0;
|
||||
int gbs_available = 8;
|
||||
int gbs_available = 8, resets_available = 4, cens_available = 4;
|
||||
for (auto &cell : ctx->cells)
|
||||
if (is_gbuf(ctx, cell.second.get()))
|
||||
if (is_gbuf(ctx, cell.second.get())) {
|
||||
/* One less buffer available */
|
||||
--gbs_available;
|
||||
|
||||
/* And possibly limits what we can promote */
|
||||
if (cell.second->attrs.find(ctx->id("BEL")) != cell.second->attrs.end()) {
|
||||
/* If the SB_GB is locked, doesn't matter what it drives */
|
||||
BelId bel = ctx->getBelByName(ctx->id(cell.second->attrs[ctx->id("BEL")]));
|
||||
int glb_id = ctx->getDrivenGlobalNetwork(bel);
|
||||
if ((glb_id % 2) == 0)
|
||||
resets_available--;
|
||||
else if ((glb_id % 2) == 1)
|
||||
cens_available--;
|
||||
} else {
|
||||
/* If it's free to move around, then look at what it drives */
|
||||
NetInfo *ni = cell.second->ports[id_GLOBAL_BUFFER_OUTPUT].net;
|
||||
|
||||
for (auto user : ni->users) {
|
||||
if (is_reset_port(ctx, user)) {
|
||||
resets_available--;
|
||||
break;
|
||||
} else if (is_enable_port(ctx, user)) {
|
||||
cens_available--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (prom_globals < gbs_available) {
|
||||
auto global_clock = std::max_element(clock_count.begin(), clock_count.end(),
|
||||
[](const std::pair<IdString, int> &a, const std::pair<IdString, int> &b) {
|
||||
@ -610,8 +636,8 @@ static void promote_globals(Context *ctx)
|
||||
return a.second < b.second;
|
||||
});
|
||||
if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh &&
|
||||
(global_logic->second > global_cen->second || prom_cens >= 4) &&
|
||||
(global_logic->second > global_reset->second || prom_resets >= 4)) {
|
||||
(global_logic->second > global_cen->second || prom_cens >= cens_available) &&
|
||||
(global_logic->second > global_reset->second || prom_resets >= resets_available)) {
|
||||
NetInfo *logicnet = ctx->nets[global_logic->first].get();
|
||||
insert_global(ctx, logicnet, false, false, true);
|
||||
++prom_globals;
|
||||
@ -620,7 +646,7 @@ static void promote_globals(Context *ctx)
|
||||
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 < 4) {
|
||||
} else if (global_reset->second > global_clock->second && prom_resets < resets_available) {
|
||||
NetInfo *rstnet = ctx->nets[global_reset->first].get();
|
||||
insert_global(ctx, rstnet, true, false, false);
|
||||
++prom_globals;
|
||||
@ -629,7 +655,7 @@ static void promote_globals(Context *ctx)
|
||||
reset_count.erase(rstnet->name);
|
||||
cen_count.erase(rstnet->name);
|
||||
logic_count.erase(rstnet->name);
|
||||
} else if (global_cen->second > global_clock->second && prom_cens < 4 &&
|
||||
} 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);
|
||||
@ -874,7 +900,7 @@ static void pack_special(Context *ctx)
|
||||
newname = "PLLOUT_A";
|
||||
if (pi.name == ctx->id("PLLOUTCOREB"))
|
||||
newname = "PLLOUT_B";
|
||||
if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALA"))
|
||||
if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBAL"))
|
||||
newname = "PLLOUT_A_GLOBAL";
|
||||
if (pi.name == ctx->id("PLLOUTGLOBALB"))
|
||||
newname = "PLLOUT_B_GLOBAL";
|
||||
@ -987,6 +1013,8 @@ static void pack_special(Context *ctx)
|
||||
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);
|
||||
ctx->nets.erase(pad_packagepin_net->name);
|
||||
pad_packagepin_net = nullptr;
|
||||
}
|
||||
|
12
ice40/pcf.cc
12
ice40/pcf.cc
@ -33,7 +33,9 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
|
||||
if (!in)
|
||||
log_error("failed to open PCF file\n");
|
||||
std::string line;
|
||||
int lineno = 0;
|
||||
while (std::getline(in, line)) {
|
||||
lineno++;
|
||||
size_t cstart = line.find("#");
|
||||
if (cstart != std::string::npos)
|
||||
line = line.substr(0, cstart);
|
||||
@ -49,21 +51,25 @@ bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
|
||||
size_t args_end = 1;
|
||||
while (args_end < words.size() && words.at(args_end).at(0) == '-')
|
||||
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 pcf constraint %s\n", cell.c_str());
|
||||
log_warning("unmatched constraint '%s' (on line %d)\n", cell.c_str(), lineno);
|
||||
} else {
|
||||
BelId pin_bel = ctx->getPackagePinBel(pin);
|
||||
if (pin_bel == BelId())
|
||||
log_error("package does not have a pin named %s\n", pin.c_str());
|
||||
log_error("package does not have a pin named '%s' (on line %d)\n", pin.c_str(), lineno);
|
||||
if (fnd_cell->second->attrs.count(ctx->id("BEL")))
|
||||
log_error("duplicate pin constraint on '%s' (on line %d)\n", cell.c_str(), lineno);
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
log_error("unsupported pcf command '%s'\n", cmd.c_str());
|
||||
log_error("unsupported PCF command '%s' (on line %d)\n", cmd.c_str(), lineno);
|
||||
}
|
||||
}
|
||||
ctx->settings.emplace(ctx->id("input/pcf"), filename);
|
||||
|
@ -472,12 +472,7 @@ void json_import_ports(Context *ctx, const string &modname, const std::vector<Id
|
||||
vcc_net(ctx, net.get());
|
||||
|
||||
} else if (wire_node->data_string.compare(string("x")) == 0) {
|
||||
|
||||
ground_net(ctx, net.get());
|
||||
log_info(" Floating wire node value, "
|
||||
"'%s' on '%s'/'%s', converted to zero driver\n",
|
||||
this_port.name.c_str(ctx), modname.c_str(), obj_name.c_str());
|
||||
|
||||
} else
|
||||
log_error(" Unknown fixed type wire node "
|
||||
"value, \'%s\'\n",
|
||||
|
Loading…
Reference in New Issue
Block a user