diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..38e14513 --- /dev/null +++ b/.cirrus.yml @@ -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 \ No newline at end of file diff --git a/.cirrus/Dockerfile.ubuntu16.04 b/.cirrus/Dockerfile.ubuntu16.04 new file mode 100644 index 00000000..71a634bb --- /dev/null +++ b/.cirrus/Dockerfile.ubuntu16.04 @@ -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 + + diff --git a/3rdparty/python-console/modified/pyinterpreter.cc b/3rdparty/python-console/modified/pyinterpreter.cc index f53207ad..89c9b88c 100644 --- a/3rdparty/python-console/modified/pyinterpreter.cc +++ b/3rdparty/python-console/modified/pyinterpreter.cc @@ -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; +} diff --git a/3rdparty/python-console/modified/pyinterpreter.h b/3rdparty/python-console/modified/pyinterpreter.h index 1a85c1fb..48512507 100644 --- a/3rdparty/python-console/modified/pyinterpreter.h +++ b/3rdparty/python-console/modified/pyinterpreter.h @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d6cd312..c75a32fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index 86f7ef2d..5b79d1fb 100644 --- a/README.md +++ b/README.md @@ -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: ``` diff --git a/common/chain_utils.h b/common/chain_utils.h index b783e30b..300d96a1 100644 --- a/common/chain_utils.h +++ b/common/chain_utils.h @@ -51,7 +51,8 @@ std::vector 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) { diff --git a/common/command.cc b/common/command.cc index 206a4d30..d332375a 100644 --- a/common/command.cc +++ b/common/command.cc @@ -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"); diff --git a/common/log.cc b/common/log.cc index 0a75b020..01aec79a 100644 --- a/common/log.cc +++ b/common/log.cc @@ -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; } diff --git a/common/log.h b/common/log.h index 52158f18..5745df0d 100644 --- a/common/log.h +++ b/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> log_streams; diff --git a/common/place_common.cc b/common/place_common.cc index b3eb4267..2a18a1a2 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -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; diff --git a/common/router1.cc b/common/router1.cc index cbc0df90..61075d84 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -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; diff --git a/common/rulecheck.cc b/common/rulecheck.cc index c696e548..1db9ae00 100644 --- a/common/rulecheck.cc +++ b/common/rulecheck.cc @@ -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(); diff --git a/common/timing.cc b/common/timing.cc index 88ab14c2..b27dd56e 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -86,6 +86,7 @@ struct CriticalPath }; typedef std::unordered_map CriticalPathMap; +typedef std::unordered_map 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 min_required; std::unordered_map 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::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::max()); + delay_t net_min_required = std::numeric_limits::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::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 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::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 diff --git a/common/timing.h b/common/timing.h index 42f928dc..f1d18e8a 100644 --- a/common/timing.h +++ b/common/timing.h @@ -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 slack; + std::vector criticality; + unsigned max_path_length = 0; + delay_t cd_worst_slack = std::numeric_limits::max(); +}; + +typedef std::unordered_map NetCriticalityMap; +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit); + NEXTPNR_NAMESPACE_END #endif diff --git a/common/timing_opt.cc b/common/timing_opt.cc new file mode 100644 index 00000000..c0968db7 --- /dev/null +++ b/common/timing_opt.cc @@ -0,0 +1,624 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * 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 +#include +#include "nextpnr.h" +#include "timing.h" +#include "util.h" + +namespace std { + +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const + noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; + +template <> struct hash> +{ + std::size_t operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; +#ifndef ARCH_GENERIC +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(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::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 + bool acceptable_move(std::vector> &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{}; + 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 free_bels_at_loc; + std::vector 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 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> find_crit_paths(float crit_thresh, size_t max_count) + { + std::vector> crit_paths; + std::vector> crit_nets; + std::vector netnames; + std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), + [](const std::pair> &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 used_ports; + + for (auto crit_net : crit_nets) { + + if (used_ports.count(&(crit_net.first->users.at(crit_net.second)))) + continue; + + std::deque 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 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 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 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 &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> cumul_costs; + std::unordered_map, std::pair> backtrace; + std::queue> visit; + std::unordered_set> 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> 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> 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> 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 &a, const std::pair &b) { + return a.second < b.second; + }); + NPNR_ASSERT(lowest != end_options.end()); + + std::vector> 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 path_cells; + std::unordered_map> cell_neighbour_bels; + std::unordered_map> bel_candidate_cells; + // Map cell ports to net delay limit + std::unordered_map, 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 diff --git a/common/timing_opt.h b/common/timing_opt.h new file mode 100644 index 00000000..ceb35c71 --- /dev/null +++ b/common/timing_opt.h @@ -0,0 +1,37 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * 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 cellTypes; +}; + +extern bool timing_opt(Context *ctx, TimingOptCfg cfg); + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 22b350c7..380c0d7d 100644 --- a/ecp5/arch.cc +++ b/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; } diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index df16946d..5031dab8 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -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); diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 31839ee4..a8e92083 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -41,6 +41,22 @@ std::unique_ptr 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 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 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::vectortype == 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 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")); diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 250bc3fc..202a7eb2 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -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) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 66c62024..49947b20 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -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 visit; std::unordered_map 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; diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 78bf7a87..84fce1c7 100644 --- a/ecp5/pack.cc +++ b/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 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::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 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 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(); diff --git a/gui/application.cc b/gui/application.cc index ccbf5645..738b7c68 100644 --- a/gui/application.cc +++ b/gui/application.cc @@ -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); diff --git a/gui/base.qrc b/gui/base.qrc index 8f58f585..644b16a6 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -22,5 +22,6 @@ resources/route.png resources/time_add.png resources/open_json.png + resources/py.png diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 49c2d8d5..346efb88 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -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() { diff --git a/gui/basewindow.h b/gui/basewindow.h index eb32033a..0b2d3fbc 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -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; diff --git a/gui/pyconsole.cc b/gui/pyconsole.cc index 0ee393ce..d015aea2 100644 --- a/gui/pyconsole.cc +++ b/gui/pyconsole.cc @@ -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 diff --git a/gui/pyconsole.h b/gui/pyconsole.h index 9dbd3b95..977242f3 100644 --- a/gui/pyconsole.h +++ b/gui/pyconsole.h @@ -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; diff --git a/gui/pythontab.cc b/gui/pythontab.cc index 80d731e9..827f1907 100644 --- a/gui/pythontab.cc +++ b/gui/pythontab.cc @@ -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 diff --git a/gui/pythontab.h b/gui/pythontab.h index 134874b6..860bf1c3 100644 --- a/gui/pythontab.h +++ b/gui/pythontab.h @@ -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; diff --git a/gui/resources/py.png b/gui/resources/py.png new file mode 100644 index 00000000..9cc6e522 Binary files /dev/null and b/gui/resources/py.png differ diff --git a/gui/worker.cc b/gui/worker.cc index b009ecd3..900883d4 100644 --- a/gui/worker.cc +++ b/gui/worker.cc @@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false) TaskManager::~TaskManager() { + log_write_function = nullptr; if (workerThread.isRunning()) terminate_thread(); workerThread.quit(); diff --git a/ice40/arch.cc b/ice40/arch.cc index 02e5515b..8f52987c 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -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 diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index ecb26753..141c218b 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -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); diff --git a/ice40/cells.cc b/ice40/cells.cc index dbb75c2c..35a5346f 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -260,6 +260,21 @@ std::unique_ptr 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)); } diff --git a/ice40/cells.h b/ice40/cells.h index 1fbd9073..93ef3db4 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -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") || diff --git a/ice40/chains.cc b/ice40/chains.cc index fb361d2d..b3b54d6b 100644 --- a/ice40/chains.cc +++ b/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 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 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 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 carry_chains = - find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, - [](const Context *ctx, const + std::vector 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 chained; for (auto &base_chain : carry_chains) { for (auto c : base_chain.cells) diff --git a/ice40/main.cc b/ice40/main.cc index fcc56d04..286f68db 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -65,7 +65,13 @@ po::options_description Ice40CommandHandler::getArchOptions() specific.add_options()("pcf", po::value(), "PCF constraints file to ingest"); specific.add_options()("asc", po::value(), "asc bitstream file to write"); specific.add_options()("read", po::value(), "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 Ice40CommandHandler::createContext() if (vm.count("package")) chipArgs.package = vm["package"].as(); - return std::unique_ptr(new Context(chipArgs)); + auto ctx = std::unique_ptr(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[]) diff --git a/ice40/pack.cc b/ice40/pack.cc index fc28121e..27387a75 100644 --- a/ice40/pack.cc +++ b/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 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 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> pll_all_bels; + std::map pll_used_bels; + std::vector pll_cells; + std::map 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 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(); diff --git a/ice40/pcf.cc b/ice40/pcf.cc index ce453af9..a8273dd6 100644 --- a/ice40/pcf.cc +++ b/ice40/pcf.cc @@ -21,6 +21,7 @@ #include "pcf.h" #include #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> 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) { diff --git a/ice40/picorv32_benchmark.py b/ice40/picorv32_benchmark.py index a4ec581e..5e4fc2e1 100755 --- a/ice40/picorv32_benchmark.py +++ b/ice40/picorv32_benchmark.py @@ -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: diff --git a/ice40/smoketest/attosoc/.gitignore b/ice40/smoketest/attosoc/.gitignore new file mode 100644 index 00000000..7acb082f --- /dev/null +++ b/ice40/smoketest/attosoc/.gitignore @@ -0,0 +1,6 @@ +attosoc_pnr.v +attosoc.asc +attosoc.json +attosoc_pnr_tb +testbench.vcd +output.txt diff --git a/ice40/smoketest/attosoc/attosoc.pcf b/ice40/smoketest/attosoc/attosoc.pcf new file mode 100644 index 00000000..4f283d45 --- /dev/null +++ b/ice40/smoketest/attosoc/attosoc.pcf @@ -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 + diff --git a/ice40/smoketest/attosoc/attosoc.v b/ice40/smoketest/attosoc/attosoc.v new file mode 100644 index 00000000..4921e298 --- /dev/null +++ b/ice40/smoketest/attosoc/attosoc.v @@ -0,0 +1,127 @@ +/* + * ECP5 PicoRV32 demo + * + * Copyright (C) 2017 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * 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 diff --git a/ice40/smoketest/attosoc/attosoc_tb.v b/ice40/smoketest/attosoc/attosoc_tb.v new file mode 100644 index 00000000..65496fcd --- /dev/null +++ b/ice40/smoketest/attosoc/attosoc_tb.v @@ -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 diff --git a/ice40/smoketest/attosoc/firmware.hex b/ice40/smoketest/attosoc/firmware.hex new file mode 100644 index 00000000..dfa64022 --- /dev/null +++ b/ice40/smoketest/attosoc/firmware.hex @@ -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 diff --git a/ice40/smoketest/attosoc/golden.txt b/ice40/smoketest/attosoc/golden.txt new file mode 100644 index 00000000..0c92568d --- /dev/null +++ b/ice40/smoketest/attosoc/golden.txt @@ -0,0 +1,16 @@ +00000000 +00000010 +00000011 +00000101 +00000111 +00001011 +00001101 +00010001 +00010011 +00010111 +00011101 +00011111 +00100101 +00101001 +00101011 +00101111 diff --git a/ice40/smoketest/attosoc/picorv32.v b/ice40/smoketest/attosoc/picorv32.v new file mode 100644 index 00000000..1aed4121 --- /dev/null +++ b/ice40/smoketest/attosoc/picorv32.v @@ -0,0 +1,2979 @@ +/* + * PicoRV32 -- A Small RISC-V (RV32I) Processor Core + * + * Copyright (C) 2015 Clifford Wolf + * + * 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. + * + */ + +`timescale 1 ns / 1 ps +// `default_nettype none +// `define DEBUGNETS +// `define DEBUGREGS +// `define DEBUGASM +// `define DEBUG + +`ifdef DEBUG + `define debug(debug_command) debug_command +`else + `define debug(debug_command) +`endif + +`ifdef FORMAL + `define FORMAL_KEEP (* keep *) + `define assert(assert_expr) assert(assert_expr) +`else + `ifdef DEBUGNETS + `define FORMAL_KEEP (* keep *) + `else + `define FORMAL_KEEP + `endif + `define assert(assert_expr) empty_statement +`endif + +// uncomment this for register file in extra module +// `define PICORV32_REGS picorv32_regs + +// this macro can be used to check if the verilog files in your +// design are read in the correct order. +`define PICORV32_V + + +/*************************************************************** + * picorv32 + ***************************************************************/ + +module picorv32 #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] LATCHED_MEM_RDATA = 0, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output reg trap, + + output reg mem_valid, + output reg mem_instr, + input mem_ready, + + output reg [31:0] mem_addr, + output reg [31:0] mem_wdata, + output reg [ 3:0] mem_wstrb, + input [31:0] mem_rdata, + + // Look-Ahead Interface + output mem_la_read, + output mem_la_write, + output [31:0] mem_la_addr, + output reg [31:0] mem_la_wdata, + output reg [ 3:0] mem_la_wstrb, + + // Pico Co-Processor Interface (PCPI) + output reg pcpi_valid, + output reg [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ Interface + input [31:0] irq, + output reg [31:0] eoi, + +`ifdef RISCV_FORMAL + output reg rvfi_valid, + output reg [63:0] rvfi_order, + output reg [31:0] rvfi_insn, + output reg rvfi_trap, + output reg rvfi_halt, + output reg rvfi_intr, + output reg [ 1:0] rvfi_mode, + output reg [ 4:0] rvfi_rs1_addr, + output reg [ 4:0] rvfi_rs2_addr, + output reg [31:0] rvfi_rs1_rdata, + output reg [31:0] rvfi_rs2_rdata, + output reg [ 4:0] rvfi_rd_addr, + output reg [31:0] rvfi_rd_wdata, + output reg [31:0] rvfi_pc_rdata, + output reg [31:0] rvfi_pc_wdata, + output reg [31:0] rvfi_mem_addr, + output reg [ 3:0] rvfi_mem_rmask, + output reg [ 3:0] rvfi_mem_wmask, + output reg [31:0] rvfi_mem_rdata, + output reg [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output reg trace_valid, + output reg [35:0] trace_data +); + localparam integer irq_timer = 0; + localparam integer irq_ebreak = 1; + localparam integer irq_buserror = 2; + + localparam integer irqregs_offset = ENABLE_REGS_16_31 ? 32 : 16; + localparam integer regfile_size = (ENABLE_REGS_16_31 ? 32 : 16) + 4*ENABLE_IRQ*ENABLE_IRQ_QREGS; + localparam integer regindex_bits = (ENABLE_REGS_16_31 ? 5 : 4) + ENABLE_IRQ*ENABLE_IRQ_QREGS; + + localparam WITH_PCPI = ENABLE_PCPI || ENABLE_MUL || ENABLE_FAST_MUL || ENABLE_DIV; + + localparam [35:0] TRACE_BRANCH = {4'b 0001, 32'b 0}; + localparam [35:0] TRACE_ADDR = {4'b 0010, 32'b 0}; + localparam [35:0] TRACE_IRQ = {4'b 1000, 32'b 0}; + + reg [63:0] count_cycle, count_instr; + reg [31:0] reg_pc, reg_next_pc, reg_op1, reg_op2, reg_out; + reg [4:0] reg_sh; + + reg [31:0] next_insn_opcode; + reg [31:0] dbg_insn_opcode; + reg [31:0] dbg_insn_addr; + + wire dbg_mem_valid = mem_valid; + wire dbg_mem_instr = mem_instr; + wire dbg_mem_ready = mem_ready; + wire [31:0] dbg_mem_addr = mem_addr; + wire [31:0] dbg_mem_wdata = mem_wdata; + wire [ 3:0] dbg_mem_wstrb = mem_wstrb; + wire [31:0] dbg_mem_rdata = mem_rdata; + + assign pcpi_rs1 = reg_op1; + assign pcpi_rs2 = reg_op2; + + wire [31:0] next_pc; + + reg irq_delay; + reg irq_active; + reg [31:0] irq_mask; + reg [31:0] irq_pending; + reg [31:0] timer; + +`ifndef PICORV32_REGS + reg [31:0] cpuregs [0:regfile_size-1]; + + integer i; + initial begin + if (REGS_INIT_ZERO) begin + for (i = 0; i < regfile_size; i = i+1) + cpuregs[i] = 0; + end + end +`endif + + task empty_statement; + // This task is used by the `assert directive in non-formal mode to + // avoid empty statement (which are unsupported by plain Verilog syntax). + begin end + endtask + +`ifdef DEBUGREGS + wire [31:0] dbg_reg_x0 = 0; + wire [31:0] dbg_reg_x1 = cpuregs[1]; + wire [31:0] dbg_reg_x2 = cpuregs[2]; + wire [31:0] dbg_reg_x3 = cpuregs[3]; + wire [31:0] dbg_reg_x4 = cpuregs[4]; + wire [31:0] dbg_reg_x5 = cpuregs[5]; + wire [31:0] dbg_reg_x6 = cpuregs[6]; + wire [31:0] dbg_reg_x7 = cpuregs[7]; + wire [31:0] dbg_reg_x8 = cpuregs[8]; + wire [31:0] dbg_reg_x9 = cpuregs[9]; + wire [31:0] dbg_reg_x10 = cpuregs[10]; + wire [31:0] dbg_reg_x11 = cpuregs[11]; + wire [31:0] dbg_reg_x12 = cpuregs[12]; + wire [31:0] dbg_reg_x13 = cpuregs[13]; + wire [31:0] dbg_reg_x14 = cpuregs[14]; + wire [31:0] dbg_reg_x15 = cpuregs[15]; + wire [31:0] dbg_reg_x16 = cpuregs[16]; + wire [31:0] dbg_reg_x17 = cpuregs[17]; + wire [31:0] dbg_reg_x18 = cpuregs[18]; + wire [31:0] dbg_reg_x19 = cpuregs[19]; + wire [31:0] dbg_reg_x20 = cpuregs[20]; + wire [31:0] dbg_reg_x21 = cpuregs[21]; + wire [31:0] dbg_reg_x22 = cpuregs[22]; + wire [31:0] dbg_reg_x23 = cpuregs[23]; + wire [31:0] dbg_reg_x24 = cpuregs[24]; + wire [31:0] dbg_reg_x25 = cpuregs[25]; + wire [31:0] dbg_reg_x26 = cpuregs[26]; + wire [31:0] dbg_reg_x27 = cpuregs[27]; + wire [31:0] dbg_reg_x28 = cpuregs[28]; + wire [31:0] dbg_reg_x29 = cpuregs[29]; + wire [31:0] dbg_reg_x30 = cpuregs[30]; + wire [31:0] dbg_reg_x31 = cpuregs[31]; +`endif + + // Internal PCPI Cores + + wire pcpi_mul_wr; + wire [31:0] pcpi_mul_rd; + wire pcpi_mul_wait; + wire pcpi_mul_ready; + + wire pcpi_div_wr; + wire [31:0] pcpi_div_rd; + wire pcpi_div_wait; + wire pcpi_div_ready; + + reg pcpi_int_wr; + reg [31:0] pcpi_int_rd; + reg pcpi_int_wait; + reg pcpi_int_ready; + + generate if (ENABLE_FAST_MUL) begin + picorv32_pcpi_fast_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else if (ENABLE_MUL) begin + picorv32_pcpi_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else begin + assign pcpi_mul_wr = 0; + assign pcpi_mul_rd = 32'bx; + assign pcpi_mul_wait = 0; + assign pcpi_mul_ready = 0; + end endgenerate + + generate if (ENABLE_DIV) begin + picorv32_pcpi_div pcpi_div ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_div_wr ), + .pcpi_rd (pcpi_div_rd ), + .pcpi_wait (pcpi_div_wait ), + .pcpi_ready(pcpi_div_ready ) + ); + end else begin + assign pcpi_div_wr = 0; + assign pcpi_div_rd = 32'bx; + assign pcpi_div_wait = 0; + assign pcpi_div_ready = 0; + end endgenerate + + always @* begin + pcpi_int_wr = 0; + pcpi_int_rd = 32'bx; + pcpi_int_wait = |{ENABLE_PCPI && pcpi_wait, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_wait, ENABLE_DIV && pcpi_div_wait}; + pcpi_int_ready = |{ENABLE_PCPI && pcpi_ready, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready, ENABLE_DIV && pcpi_div_ready}; + + (* parallel_case *) + case (1'b1) + ENABLE_PCPI && pcpi_ready: begin + pcpi_int_wr = ENABLE_PCPI ? pcpi_wr : 0; + pcpi_int_rd = ENABLE_PCPI ? pcpi_rd : 0; + end + (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready: begin + pcpi_int_wr = pcpi_mul_wr; + pcpi_int_rd = pcpi_mul_rd; + end + ENABLE_DIV && pcpi_div_ready: begin + pcpi_int_wr = pcpi_div_wr; + pcpi_int_rd = pcpi_div_rd; + end + endcase + end + + + // Memory Interface + + reg [1:0] mem_state; + reg [1:0] mem_wordsize; + reg [31:0] mem_rdata_word; + reg [31:0] mem_rdata_q; + reg mem_do_prefetch; + reg mem_do_rinst; + reg mem_do_rdata; + reg mem_do_wdata; + + wire mem_xfer; + reg mem_la_secondword, mem_la_firstword_reg, last_mem_valid; + wire mem_la_firstword = COMPRESSED_ISA && (mem_do_prefetch || mem_do_rinst) && next_pc[1] && !mem_la_secondword; + wire mem_la_firstword_xfer = COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg); + + reg prefetched_high_word; + reg clear_prefetched_high_word; + reg [15:0] mem_16bit_buffer; + + wire [31:0] mem_rdata_latched_noshuffle; + wire [31:0] mem_rdata_latched; + + wire mem_la_use_prefetched_high_word = COMPRESSED_ISA && mem_la_firstword && prefetched_high_word && !clear_prefetched_high_word; + assign mem_xfer = (mem_valid && mem_ready) || (mem_la_use_prefetched_high_word && mem_do_rinst); + + wire mem_busy = |{mem_do_prefetch, mem_do_rinst, mem_do_rdata, mem_do_wdata}; + wire mem_done = resetn && ((mem_xfer && |mem_state && (mem_do_rinst || mem_do_rdata || mem_do_wdata)) || (&mem_state && mem_do_rinst)) && + (!mem_la_firstword || (~&mem_rdata_latched[1:0] && mem_xfer)); + + assign mem_la_write = resetn && !mem_state && mem_do_wdata; + assign mem_la_read = resetn && ((!mem_la_use_prefetched_high_word && !mem_state && (mem_do_rinst || mem_do_prefetch || mem_do_rdata)) || + (COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg) && !mem_la_secondword && &mem_rdata_latched[1:0])); + assign mem_la_addr = (mem_do_prefetch || mem_do_rinst) ? {next_pc[31:2] + mem_la_firstword_xfer, 2'b00} : {reg_op1[31:2], 2'b00}; + + assign mem_rdata_latched_noshuffle = (mem_xfer || LATCHED_MEM_RDATA) ? mem_rdata : mem_rdata_q; + + assign mem_rdata_latched = COMPRESSED_ISA && mem_la_use_prefetched_high_word ? {16'bx, mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_secondword ? {mem_rdata_latched_noshuffle[15:0], mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_firstword ? {16'bx, mem_rdata_latched_noshuffle[31:16]} : mem_rdata_latched_noshuffle; + + always @(posedge clk) begin + if (!resetn) begin + mem_la_firstword_reg <= 0; + last_mem_valid <= 0; + end else begin + if (!last_mem_valid) + mem_la_firstword_reg <= mem_la_firstword; + last_mem_valid <= mem_valid && !mem_ready; + end + end + + always @* begin + (* full_case *) + case (mem_wordsize) + 0: begin + mem_la_wdata = reg_op2; + mem_la_wstrb = 4'b1111; + mem_rdata_word = mem_rdata; + end + 1: begin + mem_la_wdata = {2{reg_op2[15:0]}}; + mem_la_wstrb = reg_op1[1] ? 4'b1100 : 4'b0011; + case (reg_op1[1]) + 1'b0: mem_rdata_word = {16'b0, mem_rdata[15: 0]}; + 1'b1: mem_rdata_word = {16'b0, mem_rdata[31:16]}; + endcase + end + 2: begin + mem_la_wdata = {4{reg_op2[7:0]}}; + mem_la_wstrb = 4'b0001 << reg_op1[1:0]; + case (reg_op1[1:0]) + 2'b00: mem_rdata_word = {24'b0, mem_rdata[ 7: 0]}; + 2'b01: mem_rdata_word = {24'b0, mem_rdata[15: 8]}; + 2'b10: mem_rdata_word = {24'b0, mem_rdata[23:16]}; + 2'b11: mem_rdata_word = {24'b0, mem_rdata[31:24]}; + endcase + end + endcase + end + + always @(posedge clk) begin + if (mem_xfer) begin + mem_rdata_q <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + next_insn_opcode <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + end + + if (COMPRESSED_ISA && mem_done && (mem_do_prefetch || mem_do_rinst)) begin + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= {2'b0, mem_rdata_latched[10:7], mem_rdata_latched[12:11], mem_rdata_latched[5], mem_rdata_latched[6], 2'b00}; + end + 3'b010: begin // C.LW + mem_rdata_q[31:20] <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b 110: begin // C.SW + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b 000: begin // C.ADDI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 010: begin // C.LI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 011: begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[4:3], + mem_rdata_latched[5], mem_rdata_latched[2], mem_rdata_latched[6], 4'b 0000}); + end else begin // C.LUI + mem_rdata_q[31:12] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + end + 3'b100: begin + if (mem_rdata_latched[11:10] == 2'b00) begin // C.SRLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b01) begin // C.SRAI + mem_rdata_q[31:25] <= 7'b0100000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + if (mem_rdata_latched[6:5] == 2'b00) mem_rdata_q[14:12] <= 3'b000; + if (mem_rdata_latched[6:5] == 2'b01) mem_rdata_q[14:12] <= 3'b100; + if (mem_rdata_latched[6:5] == 2'b10) mem_rdata_q[14:12] <= 3'b110; + if (mem_rdata_latched[6:5] == 2'b11) mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:25] <= mem_rdata_latched[6:5] == 2'b00 ? 7'b0100000 : 7'b0000000; + end + end + 3'b 110: begin // C.BEQZ + mem_rdata_q[14:12] <= 3'b000; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + 3'b 111: begin // C.BNEZ + mem_rdata_q[14:12] <= 3'b001; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 001; + end + 3'b010: begin // C.LWSP + mem_rdata_q[31:20] <= {4'b0, mem_rdata_latched[3:2], mem_rdata_latched[12], mem_rdata_latched[6:4], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + end + 3'b110: begin // C.SWSP + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {4'b0, mem_rdata_latched[8:7], mem_rdata_latched[12:9], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + endcase + end + end + + always @(posedge clk) begin + if (resetn && !trap) begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) + `assert(!mem_do_wdata); + + if (mem_do_prefetch || mem_do_rinst) + `assert(!mem_do_rdata); + + if (mem_do_rdata) + `assert(!mem_do_prefetch && !mem_do_rinst); + + if (mem_do_wdata) + `assert(!(mem_do_prefetch || mem_do_rinst || mem_do_rdata)); + + if (mem_state == 2 || mem_state == 3) + `assert(mem_valid || mem_do_prefetch); + end + end + + always @(posedge clk) begin + if (!resetn || trap) begin + if (!resetn) + mem_state <= 0; + if (!resetn || mem_ready) + mem_valid <= 0; + mem_la_secondword <= 0; + prefetched_high_word <= 0; + end else begin + if (mem_la_read || mem_la_write) begin + mem_addr <= mem_la_addr; + mem_wstrb <= mem_la_wstrb & {4{mem_la_write}}; + end + if (mem_la_write) begin + mem_wdata <= mem_la_wdata; + end + case (mem_state) + 0: begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) begin + mem_valid <= !mem_la_use_prefetched_high_word; + mem_instr <= mem_do_prefetch || mem_do_rinst; + mem_wstrb <= 0; + mem_state <= 1; + end + if (mem_do_wdata) begin + mem_valid <= 1; + mem_instr <= 0; + mem_state <= 2; + end + end + 1: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch || mem_do_rinst || mem_do_rdata); + `assert(mem_valid == !mem_la_use_prefetched_high_word); + `assert(mem_instr == (mem_do_prefetch || mem_do_rinst)); + if (mem_xfer) begin + if (COMPRESSED_ISA && mem_la_read) begin + mem_valid <= 1; + mem_la_secondword <= 1; + if (!mem_la_use_prefetched_high_word) + mem_16bit_buffer <= mem_rdata[31:16]; + end else begin + mem_valid <= 0; + mem_la_secondword <= 0; + if (COMPRESSED_ISA && !mem_do_rdata) begin + if (~&mem_rdata[1:0] || mem_la_secondword) begin + mem_16bit_buffer <= mem_rdata[31:16]; + prefetched_high_word <= 1; + end else begin + prefetched_high_word <= 0; + end + end + mem_state <= mem_do_rinst || mem_do_rdata ? 0 : 3; + end + end + end + 2: begin + `assert(mem_wstrb != 0); + `assert(mem_do_wdata); + if (mem_xfer) begin + mem_valid <= 0; + mem_state <= 0; + end + end + 3: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch); + if (mem_do_rinst) begin + mem_state <= 0; + end + end + endcase + end + + if (clear_prefetched_high_word) + prefetched_high_word <= 0; + end + + + // Instruction Decoder + + reg instr_lui, instr_auipc, instr_jal, instr_jalr; + reg instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu; + reg instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw; + reg instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai; + reg instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and; + reg instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, instr_ecall_ebreak; + reg instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer; + wire instr_trap; + + reg [regindex_bits-1:0] decoded_rd, decoded_rs1, decoded_rs2; + reg [31:0] decoded_imm, decoded_imm_uj; + reg decoder_trigger; + reg decoder_trigger_q; + reg decoder_pseudo_trigger; + reg decoder_pseudo_trigger_q; + reg compressed_instr; + + reg is_lui_auipc_jal; + reg is_lb_lh_lw_lbu_lhu; + reg is_slli_srli_srai; + reg is_jalr_addi_slti_sltiu_xori_ori_andi; + reg is_sb_sh_sw; + reg is_sll_srl_sra; + reg is_lui_auipc_jal_jalr_addi_add_sub; + reg is_slti_blt_slt; + reg is_sltiu_bltu_sltu; + reg is_beq_bne_blt_bge_bltu_bgeu; + reg is_lbu_lhu_lw; + reg is_alu_reg_imm; + reg is_alu_reg_reg; + reg is_compare; + + assign instr_trap = (CATCH_ILLINSN || WITH_PCPI) && !{instr_lui, instr_auipc, instr_jal, instr_jalr, + instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu, + instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw, + instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai, + instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and, + instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, + instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer}; + + wire is_rdcycle_rdcycleh_rdinstr_rdinstrh; + assign is_rdcycle_rdcycleh_rdinstr_rdinstrh = |{instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh}; + + reg [63:0] new_ascii_instr; + `FORMAL_KEEP reg [63:0] dbg_ascii_instr; + `FORMAL_KEEP reg [31:0] dbg_insn_imm; + `FORMAL_KEEP reg [4:0] dbg_insn_rs1; + `FORMAL_KEEP reg [4:0] dbg_insn_rs2; + `FORMAL_KEEP reg [4:0] dbg_insn_rd; + `FORMAL_KEEP reg [31:0] dbg_rs1val; + `FORMAL_KEEP reg [31:0] dbg_rs2val; + `FORMAL_KEEP reg dbg_rs1val_valid; + `FORMAL_KEEP reg dbg_rs2val_valid; + + always @* begin + new_ascii_instr = ""; + + if (instr_lui) new_ascii_instr = "lui"; + if (instr_auipc) new_ascii_instr = "auipc"; + if (instr_jal) new_ascii_instr = "jal"; + if (instr_jalr) new_ascii_instr = "jalr"; + + if (instr_beq) new_ascii_instr = "beq"; + if (instr_bne) new_ascii_instr = "bne"; + if (instr_blt) new_ascii_instr = "blt"; + if (instr_bge) new_ascii_instr = "bge"; + if (instr_bltu) new_ascii_instr = "bltu"; + if (instr_bgeu) new_ascii_instr = "bgeu"; + + if (instr_lb) new_ascii_instr = "lb"; + if (instr_lh) new_ascii_instr = "lh"; + if (instr_lw) new_ascii_instr = "lw"; + if (instr_lbu) new_ascii_instr = "lbu"; + if (instr_lhu) new_ascii_instr = "lhu"; + if (instr_sb) new_ascii_instr = "sb"; + if (instr_sh) new_ascii_instr = "sh"; + if (instr_sw) new_ascii_instr = "sw"; + + if (instr_addi) new_ascii_instr = "addi"; + if (instr_slti) new_ascii_instr = "slti"; + if (instr_sltiu) new_ascii_instr = "sltiu"; + if (instr_xori) new_ascii_instr = "xori"; + if (instr_ori) new_ascii_instr = "ori"; + if (instr_andi) new_ascii_instr = "andi"; + if (instr_slli) new_ascii_instr = "slli"; + if (instr_srli) new_ascii_instr = "srli"; + if (instr_srai) new_ascii_instr = "srai"; + + if (instr_add) new_ascii_instr = "add"; + if (instr_sub) new_ascii_instr = "sub"; + if (instr_sll) new_ascii_instr = "sll"; + if (instr_slt) new_ascii_instr = "slt"; + if (instr_sltu) new_ascii_instr = "sltu"; + if (instr_xor) new_ascii_instr = "xor"; + if (instr_srl) new_ascii_instr = "srl"; + if (instr_sra) new_ascii_instr = "sra"; + if (instr_or) new_ascii_instr = "or"; + if (instr_and) new_ascii_instr = "and"; + + if (instr_rdcycle) new_ascii_instr = "rdcycle"; + if (instr_rdcycleh) new_ascii_instr = "rdcycleh"; + if (instr_rdinstr) new_ascii_instr = "rdinstr"; + if (instr_rdinstrh) new_ascii_instr = "rdinstrh"; + + if (instr_getq) new_ascii_instr = "getq"; + if (instr_setq) new_ascii_instr = "setq"; + if (instr_retirq) new_ascii_instr = "retirq"; + if (instr_maskirq) new_ascii_instr = "maskirq"; + if (instr_waitirq) new_ascii_instr = "waitirq"; + if (instr_timer) new_ascii_instr = "timer"; + end + + reg [63:0] q_ascii_instr; + reg [31:0] q_insn_imm; + reg [31:0] q_insn_opcode; + reg [4:0] q_insn_rs1; + reg [4:0] q_insn_rs2; + reg [4:0] q_insn_rd; + reg dbg_next; + + wire launch_next_insn; + reg dbg_valid_insn; + + reg [63:0] cached_ascii_instr; + reg [31:0] cached_insn_imm; + reg [31:0] cached_insn_opcode; + reg [4:0] cached_insn_rs1; + reg [4:0] cached_insn_rs2; + reg [4:0] cached_insn_rd; + + always @(posedge clk) begin + q_ascii_instr <= dbg_ascii_instr; + q_insn_imm <= dbg_insn_imm; + q_insn_opcode <= dbg_insn_opcode; + q_insn_rs1 <= dbg_insn_rs1; + q_insn_rs2 <= dbg_insn_rs2; + q_insn_rd <= dbg_insn_rd; + dbg_next <= launch_next_insn; + + if (!resetn || trap) + dbg_valid_insn <= 0; + else if (launch_next_insn) + dbg_valid_insn <= 1; + + if (decoder_trigger_q) begin + cached_ascii_instr <= new_ascii_instr; + cached_insn_imm <= decoded_imm; + if (&next_insn_opcode[1:0]) + cached_insn_opcode <= next_insn_opcode; + else + cached_insn_opcode <= {16'b0, next_insn_opcode[15:0]}; + cached_insn_rs1 <= decoded_rs1; + cached_insn_rs2 <= decoded_rs2; + cached_insn_rd <= decoded_rd; + end + + if (launch_next_insn) begin + dbg_insn_addr <= next_pc; + end + end + + always @* begin + dbg_ascii_instr = q_ascii_instr; + dbg_insn_imm = q_insn_imm; + dbg_insn_opcode = q_insn_opcode; + dbg_insn_rs1 = q_insn_rs1; + dbg_insn_rs2 = q_insn_rs2; + dbg_insn_rd = q_insn_rd; + + if (dbg_next) begin + if (decoder_pseudo_trigger_q) begin + dbg_ascii_instr = cached_ascii_instr; + dbg_insn_imm = cached_insn_imm; + dbg_insn_opcode = cached_insn_opcode; + dbg_insn_rs1 = cached_insn_rs1; + dbg_insn_rs2 = cached_insn_rs2; + dbg_insn_rd = cached_insn_rd; + end else begin + dbg_ascii_instr = new_ascii_instr; + if (&next_insn_opcode[1:0]) + dbg_insn_opcode = next_insn_opcode; + else + dbg_insn_opcode = {16'b0, next_insn_opcode[15:0]}; + dbg_insn_imm = decoded_imm; + dbg_insn_rs1 = decoded_rs1; + dbg_insn_rs2 = decoded_rs2; + dbg_insn_rd = decoded_rd; + end + end + end + +`ifdef DEBUGASM + always @(posedge clk) begin + if (dbg_next) begin + $display("debugasm %x %x %s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "*"); + end + end +`endif + +`ifdef DEBUG + always @(posedge clk) begin + if (dbg_next) begin + if (&dbg_insn_opcode[1:0]) + $display("DECODE: 0x%08x 0x%08x %-0s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + else + $display("DECODE: 0x%08x 0x%04x %-0s", dbg_insn_addr, dbg_insn_opcode[15:0], dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + end + end +`endif + + always @(posedge clk) begin + is_lui_auipc_jal <= |{instr_lui, instr_auipc, instr_jal}; + is_lui_auipc_jal_jalr_addi_add_sub <= |{instr_lui, instr_auipc, instr_jal, instr_jalr, instr_addi, instr_add, instr_sub}; + is_slti_blt_slt <= |{instr_slti, instr_blt, instr_slt}; + is_sltiu_bltu_sltu <= |{instr_sltiu, instr_bltu, instr_sltu}; + is_lbu_lhu_lw <= |{instr_lbu, instr_lhu, instr_lw}; + is_compare <= |{is_beq_bne_blt_bge_bltu_bgeu, instr_slti, instr_slt, instr_sltiu, instr_sltu}; + + if (mem_do_rinst && mem_done) begin + instr_lui <= mem_rdata_latched[6:0] == 7'b0110111; + instr_auipc <= mem_rdata_latched[6:0] == 7'b0010111; + instr_jal <= mem_rdata_latched[6:0] == 7'b1101111; + instr_jalr <= mem_rdata_latched[6:0] == 7'b1100111 && mem_rdata_latched[14:12] == 3'b000; + instr_retirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ; + instr_waitirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000100 && ENABLE_IRQ; + + is_beq_bne_blt_bge_bltu_bgeu <= mem_rdata_latched[6:0] == 7'b1100011; + is_lb_lh_lw_lbu_lhu <= mem_rdata_latched[6:0] == 7'b0000011; + is_sb_sh_sw <= mem_rdata_latched[6:0] == 7'b0100011; + is_alu_reg_imm <= mem_rdata_latched[6:0] == 7'b0010011; + is_alu_reg_reg <= mem_rdata_latched[6:0] == 7'b0110011; + + { decoded_imm_uj[31:20], decoded_imm_uj[10:1], decoded_imm_uj[11], decoded_imm_uj[19:12], decoded_imm_uj[0] } <= $signed({mem_rdata_latched[31:12], 1'b0}); + + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[19:15]; + decoded_rs2 <= mem_rdata_latched[24:20]; + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS) + decoded_rs1[regindex_bits-1] <= 1; // instr_getq + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ) + decoded_rs1 <= ENABLE_IRQ_QREGS ? irqregs_offset : 3; // instr_retirq + + compressed_instr <= 0; + if (COMPRESSED_ISA && mem_rdata_latched[1:0] != 2'b11) begin + compressed_instr <= 1; + decoded_rd <= 0; + decoded_rs1 <= 0; + decoded_rs2 <= 0; + + { decoded_imm_uj[31:11], decoded_imm_uj[4], decoded_imm_uj[9:8], decoded_imm_uj[10], decoded_imm_uj[6], + decoded_imm_uj[7], decoded_imm_uj[3:1], decoded_imm_uj[5], decoded_imm_uj[0] } <= $signed({mem_rdata_latched[12:2], 1'b0}); + + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + is_alu_reg_imm <= |mem_rdata_latched[12:5]; + decoded_rs1 <= 2; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b010: begin // C.LW + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b110: begin // C.SW + is_sb_sh_sw <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.NOP / C.ADDI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + 3'b001: begin // C.JAL + instr_jal <= 1; + decoded_rd <= 1; + end + 3'b 010: begin // C.LI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + 3'b 011: begin + if (mem_rdata_latched[12] || mem_rdata_latched[6:2]) begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end else begin // C.LUI + instr_lui <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + end + end + 3'b100: begin + if (!mem_rdata_latched[11] && !mem_rdata_latched[12]) begin // C.SRLI, C.SRAI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + is_alu_reg_reg <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + end + 3'b101: begin // C.J + instr_jal <= 1; + end + 3'b110: begin // C.BEQZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + 3'b111: begin // C.BNEZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + if (!mem_rdata_latched[12]) begin + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + end + 3'b010: begin // C.LWSP + if (mem_rdata_latched[11:7]) begin + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 2; + end + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + instr_jalr <= 1; + decoded_rd <= 0; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + instr_jalr <= 1; + decoded_rd <= 1; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + end + 3'b110: begin // C.SWSP + is_sb_sh_sw <= 1; + decoded_rs1 <= 2; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + endcase + end + endcase + end + end + + if (decoder_trigger && !decoder_pseudo_trigger) begin + pcpi_insn <= WITH_PCPI ? mem_rdata_q : 'bx; + + instr_beq <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b000; + instr_bne <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b001; + instr_blt <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b100; + instr_bge <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b101; + instr_bltu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b110; + instr_bgeu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b111; + + instr_lb <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b000; + instr_lh <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b001; + instr_lw <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b010; + instr_lbu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b100; + instr_lhu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b101; + + instr_sb <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b000; + instr_sh <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b001; + instr_sw <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b010; + + instr_addi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b000; + instr_slti <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b010; + instr_sltiu <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b011; + instr_xori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b100; + instr_ori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b110; + instr_andi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b111; + + instr_slli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_srli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_srai <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + + instr_add <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0000000; + instr_sub <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0100000; + instr_sll <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_slt <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b010 && mem_rdata_q[31:25] == 7'b0000000; + instr_sltu <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b011 && mem_rdata_q[31:25] == 7'b0000000; + instr_xor <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b100 && mem_rdata_q[31:25] == 7'b0000000; + instr_srl <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_sra <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + instr_or <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b110 && mem_rdata_q[31:25] == 7'b0000000; + instr_and <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b111 && mem_rdata_q[31:25] == 7'b0000000; + + instr_rdcycle <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000100000010)) && ENABLE_COUNTERS; + instr_rdcycleh <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000100000010)) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + instr_rdinstr <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000001000000010) && ENABLE_COUNTERS; + instr_rdinstrh <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000001000000010) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + + instr_ecall_ebreak <= ((mem_rdata_q[6:0] == 7'b1110011 && !mem_rdata_q[31:21] && !mem_rdata_q[19:7]) || + (COMPRESSED_ISA && mem_rdata_q[15:0] == 16'h9002)); + + instr_getq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_setq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000001 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_maskirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000011 && ENABLE_IRQ; + instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_IRQ && ENABLE_IRQ_TIMER; + + is_slli_srli_srai <= is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_jalr_addi_slti_sltiu_xori_ori_andi <= instr_jalr || is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b000, + mem_rdata_q[14:12] == 3'b010, + mem_rdata_q[14:12] == 3'b011, + mem_rdata_q[14:12] == 3'b100, + mem_rdata_q[14:12] == 3'b110, + mem_rdata_q[14:12] == 3'b111 + }; + + is_sll_srl_sra <= is_alu_reg_reg && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_lui_auipc_jal_jalr_addi_add_sub <= 0; + is_compare <= 0; + + (* parallel_case *) + case (1'b1) + instr_jal: + decoded_imm <= decoded_imm_uj; + |{instr_lui, instr_auipc}: + decoded_imm <= mem_rdata_q[31:12] << 12; + |{instr_jalr, is_lb_lh_lw_lbu_lhu, is_alu_reg_imm}: + decoded_imm <= $signed(mem_rdata_q[31:20]); + is_beq_bne_blt_bge_bltu_bgeu: + decoded_imm <= $signed({mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8], 1'b0}); + is_sb_sh_sw: + decoded_imm <= $signed({mem_rdata_q[31:25], mem_rdata_q[11:7]}); + default: + decoded_imm <= 1'bx; + endcase + end + + if (!resetn) begin + is_beq_bne_blt_bge_bltu_bgeu <= 0; + is_compare <= 0; + + instr_beq <= 0; + instr_bne <= 0; + instr_blt <= 0; + instr_bge <= 0; + instr_bltu <= 0; + instr_bgeu <= 0; + + instr_addi <= 0; + instr_slti <= 0; + instr_sltiu <= 0; + instr_xori <= 0; + instr_ori <= 0; + instr_andi <= 0; + + instr_add <= 0; + instr_sub <= 0; + instr_sll <= 0; + instr_slt <= 0; + instr_sltu <= 0; + instr_xor <= 0; + instr_srl <= 0; + instr_sra <= 0; + instr_or <= 0; + instr_and <= 0; + end + end + + + // Main State Machine + + localparam cpu_state_trap = 8'b10000000; + localparam cpu_state_fetch = 8'b01000000; + localparam cpu_state_ld_rs1 = 8'b00100000; + localparam cpu_state_ld_rs2 = 8'b00010000; + localparam cpu_state_exec = 8'b00001000; + localparam cpu_state_shift = 8'b00000100; + localparam cpu_state_stmem = 8'b00000010; + localparam cpu_state_ldmem = 8'b00000001; + + reg [7:0] cpu_state; + reg [1:0] irq_state; + + `FORMAL_KEEP reg [127:0] dbg_ascii_state; + + always @* begin + dbg_ascii_state = ""; + if (cpu_state == cpu_state_trap) dbg_ascii_state = "trap"; + if (cpu_state == cpu_state_fetch) dbg_ascii_state = "fetch"; + if (cpu_state == cpu_state_ld_rs1) dbg_ascii_state = "ld_rs1"; + if (cpu_state == cpu_state_ld_rs2) dbg_ascii_state = "ld_rs2"; + if (cpu_state == cpu_state_exec) dbg_ascii_state = "exec"; + if (cpu_state == cpu_state_shift) dbg_ascii_state = "shift"; + if (cpu_state == cpu_state_stmem) dbg_ascii_state = "stmem"; + if (cpu_state == cpu_state_ldmem) dbg_ascii_state = "ldmem"; + end + + reg set_mem_do_rinst; + reg set_mem_do_rdata; + reg set_mem_do_wdata; + + reg latched_store; + reg latched_stalu; + reg latched_branch; + reg latched_compr; + reg latched_trace; + reg latched_is_lu; + reg latched_is_lh; + reg latched_is_lb; + reg [regindex_bits-1:0] latched_rd; + + reg [31:0] current_pc; + assign next_pc = latched_store && latched_branch ? reg_out & ~1 : reg_next_pc; + + reg [3:0] pcpi_timeout_counter; + reg pcpi_timeout; + + reg [31:0] next_irq_pending; + reg do_waitirq; + + reg [31:0] alu_out, alu_out_q; + reg alu_out_0, alu_out_0_q; + reg alu_wait, alu_wait_2; + + reg [31:0] alu_add_sub; + reg [31:0] alu_shl, alu_shr; + reg alu_eq, alu_ltu, alu_lts; + + generate if (TWO_CYCLE_ALU) begin + always @(posedge clk) begin + alu_add_sub <= instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq <= reg_op1 == reg_op2; + alu_lts <= $signed(reg_op1) < $signed(reg_op2); + alu_ltu <= reg_op1 < reg_op2; + alu_shl <= reg_op1 << reg_op2[4:0]; + alu_shr <= $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end else begin + always @* begin + alu_add_sub = instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq = reg_op1 == reg_op2; + alu_lts = $signed(reg_op1) < $signed(reg_op2); + alu_ltu = reg_op1 < reg_op2; + alu_shl = reg_op1 << reg_op2[4:0]; + alu_shr = $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end endgenerate + + always @* begin + alu_out_0 = 'bx; + (* parallel_case, full_case *) + case (1'b1) + instr_beq: + alu_out_0 = alu_eq; + instr_bne: + alu_out_0 = !alu_eq; + instr_bge: + alu_out_0 = !alu_lts; + instr_bgeu: + alu_out_0 = !alu_ltu; + is_slti_blt_slt && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_lts; + is_sltiu_bltu_sltu && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_ltu; + endcase + + alu_out = 'bx; + (* parallel_case, full_case *) + case (1'b1) + is_lui_auipc_jal_jalr_addi_add_sub: + alu_out = alu_add_sub; + is_compare: + alu_out = alu_out_0; + instr_xori || instr_xor: + alu_out = reg_op1 ^ reg_op2; + instr_ori || instr_or: + alu_out = reg_op1 | reg_op2; + instr_andi || instr_and: + alu_out = reg_op1 & reg_op2; + BARREL_SHIFTER && (instr_sll || instr_slli): + alu_out = alu_shl; + BARREL_SHIFTER && (instr_srl || instr_srli || instr_sra || instr_srai): + alu_out = alu_shr; + endcase + +`ifdef RISCV_FORMAL_BLACKBOX_ALU + alu_out_0 = $anyseq; + alu_out = $anyseq; +`endif + end + + reg clear_prefetched_high_word_q; + always @(posedge clk) clear_prefetched_high_word_q <= clear_prefetched_high_word; + + always @* begin + clear_prefetched_high_word = clear_prefetched_high_word_q; + if (!prefetched_high_word) + clear_prefetched_high_word = 0; + if (latched_branch || irq_state || !resetn) + clear_prefetched_high_word = COMPRESSED_ISA; + end + + reg cpuregs_write; + reg [31:0] cpuregs_wrdata; + reg [31:0] cpuregs_rs1; + reg [31:0] cpuregs_rs2; + reg [regindex_bits-1:0] decoded_rs; + + always @* begin + cpuregs_write = 0; + cpuregs_wrdata = 'bx; + + if (cpu_state == cpu_state_fetch) begin + (* parallel_case *) + case (1'b1) + latched_branch: begin + cpuregs_wrdata = reg_pc + (latched_compr ? 2 : 4); + cpuregs_write = 1; + end + latched_store && !latched_branch: begin + cpuregs_wrdata = latched_stalu ? alu_out_q : reg_out; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[0]: begin + cpuregs_wrdata = reg_next_pc | latched_compr; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[1]: begin + cpuregs_wrdata = irq_pending & ~irq_mask; + cpuregs_write = 1; + end + endcase + end + end + +`ifndef PICORV32_REGS + always @(posedge clk) begin + if (resetn && cpuregs_write && latched_rd) + cpuregs[latched_rd] <= cpuregs_wrdata; + end + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs1 ? cpuregs[decoded_rs1] : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs[decoded_rs2] : 0; +`else + cpuregs_rs1 = decoded_rs1 ? $anyseq : 0; + cpuregs_rs2 = decoded_rs2 ? $anyseq : 0; +`endif + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs ? cpuregs[decoded_rs] : 0; +`else + cpuregs_rs1 = decoded_rs ? $anyseq : 0; +`endif + cpuregs_rs2 = cpuregs_rs1; + end + end +`else + wire[31:0] cpuregs_rdata1; + wire[31:0] cpuregs_rdata2; + + wire [5:0] cpuregs_waddr = latched_rd; + wire [5:0] cpuregs_raddr1 = ENABLE_REGS_DUALPORT ? decoded_rs1 : decoded_rs; + wire [5:0] cpuregs_raddr2 = ENABLE_REGS_DUALPORT ? decoded_rs2 : 0; + + `PICORV32_REGS cpuregs ( + .clk(clk), + .wen(resetn && cpuregs_write && latched_rd), + .waddr(cpuregs_waddr), + .raddr1(cpuregs_raddr1), + .raddr2(cpuregs_raddr2), + .wdata(cpuregs_wrdata), + .rdata1(cpuregs_rdata1), + .rdata2(cpuregs_rdata2) + ); + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin + cpuregs_rs1 = decoded_rs1 ? cpuregs_rdata1 : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs_rdata2 : 0; + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; + cpuregs_rs1 = decoded_rs ? cpuregs_rdata1 : 0; + cpuregs_rs2 = cpuregs_rs1; + end + end +`endif + + assign launch_next_insn = cpu_state == cpu_state_fetch && decoder_trigger && (!ENABLE_IRQ || irq_delay || irq_active || !(irq_pending & ~irq_mask)); + + always @(posedge clk) begin + trap <= 0; + reg_sh <= 'bx; + reg_out <= 'bx; + set_mem_do_rinst = 0; + set_mem_do_rdata = 0; + set_mem_do_wdata = 0; + + alu_out_0_q <= alu_out_0; + alu_out_q <= alu_out; + + alu_wait <= 0; + alu_wait_2 <= 0; + + if (launch_next_insn) begin + dbg_rs1val <= 'bx; + dbg_rs2val <= 'bx; + dbg_rs1val_valid <= 0; + dbg_rs2val_valid <= 0; + end + + if (WITH_PCPI && CATCH_ILLINSN) begin + if (resetn && pcpi_valid && !pcpi_int_wait) begin + if (pcpi_timeout_counter) + pcpi_timeout_counter <= pcpi_timeout_counter - 1; + end else + pcpi_timeout_counter <= ~0; + pcpi_timeout <= !pcpi_timeout_counter; + end + + if (ENABLE_COUNTERS) begin + count_cycle <= resetn ? count_cycle + 1 : 0; + if (!ENABLE_COUNTERS64) count_cycle[63:32] <= 0; + end else begin + count_cycle <= 'bx; + count_instr <= 'bx; + end + + next_irq_pending = ENABLE_IRQ ? irq_pending & LATCHED_IRQ : 'bx; + + if (ENABLE_IRQ && ENABLE_IRQ_TIMER && timer) begin + if (timer - 1 == 0) + next_irq_pending[irq_timer] = 1; + timer <= timer - 1; + end + + if (ENABLE_IRQ) begin + next_irq_pending = next_irq_pending | irq; + end + + decoder_trigger <= mem_do_rinst && mem_done; + decoder_trigger_q <= decoder_trigger; + decoder_pseudo_trigger <= 0; + decoder_pseudo_trigger_q <= decoder_pseudo_trigger; + do_waitirq <= 0; + + trace_valid <= 0; + + if (!ENABLE_TRACE) + trace_data <= 'bx; + + if (!resetn) begin + reg_pc <= PROGADDR_RESET; + reg_next_pc <= PROGADDR_RESET; + if (ENABLE_COUNTERS) + count_instr <= 0; + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_trace <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + pcpi_valid <= 0; + pcpi_timeout <= 0; + irq_active <= 0; + irq_delay <= 0; + irq_mask <= ~0; + next_irq_pending = 0; + irq_state <= 0; + eoi <= 0; + timer <= 0; + if (~STACKADDR) begin + latched_store <= 1; + latched_rd <= 2; + reg_out <= STACKADDR; + end + cpu_state <= cpu_state_fetch; + end else + (* parallel_case, full_case *) + case (cpu_state) + cpu_state_trap: begin + trap <= 1; + end + + cpu_state_fetch: begin + mem_do_rinst <= !decoder_trigger && !do_waitirq; + mem_wordsize <= 0; + + current_pc = reg_next_pc; + + (* parallel_case *) + case (1'b1) + latched_branch: begin + current_pc = latched_store ? (latched_stalu ? alu_out_q : reg_out) & ~1 : reg_next_pc; + `debug($display("ST_RD: %2d 0x%08x, BRANCH 0x%08x", latched_rd, reg_pc + (latched_compr ? 2 : 4), current_pc);) + end + latched_store && !latched_branch: begin + `debug($display("ST_RD: %2d 0x%08x", latched_rd, latched_stalu ? alu_out_q : reg_out);) + end + ENABLE_IRQ && irq_state[0]: begin + current_pc = PROGADDR_IRQ; + irq_active <= 1; + mem_do_rinst <= 1; + end + ENABLE_IRQ && irq_state[1]: begin + eoi <= irq_pending & ~irq_mask; + next_irq_pending = next_irq_pending & irq_mask; + end + endcase + + if (ENABLE_TRACE && latched_trace) begin + latched_trace <= 0; + trace_valid <= 1; + if (latched_branch) + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_BRANCH | (current_pc & 32'hfffffffe); + else + trace_data <= (irq_active ? TRACE_IRQ : 0) | (latched_stalu ? alu_out_q : reg_out); + end + + reg_pc <= current_pc; + reg_next_pc <= current_pc; + + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + latched_rd <= decoded_rd; + latched_compr <= compressed_instr; + + if (ENABLE_IRQ && ((decoder_trigger && !irq_active && !irq_delay && |(irq_pending & ~irq_mask)) || irq_state)) begin + irq_state <= + irq_state == 2'b00 ? 2'b01 : + irq_state == 2'b01 ? 2'b10 : 2'b00; + latched_compr <= latched_compr; + if (ENABLE_IRQ_QREGS) + latched_rd <= irqregs_offset | irq_state[0]; + else + latched_rd <= irq_state[0] ? 4 : 3; + end else + if (ENABLE_IRQ && (decoder_trigger || do_waitirq) && instr_waitirq) begin + if (irq_pending) begin + latched_store <= 1; + reg_out <= irq_pending; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + mem_do_rinst <= 1; + end else + do_waitirq <= 1; + end else + if (decoder_trigger) begin + `debug($display("-- %-0t", $time);) + irq_delay <= irq_active; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + if (ENABLE_TRACE) + latched_trace <= 1; + if (ENABLE_COUNTERS) begin + count_instr <= count_instr + 1; + if (!ENABLE_COUNTERS64) count_instr[63:32] <= 0; + end + if (instr_jal) begin + mem_do_rinst <= 1; + reg_next_pc <= current_pc + decoded_imm_uj; + latched_branch <= 1; + end else begin + mem_do_rinst <= 0; + mem_do_prefetch <= !instr_jalr && !instr_retirq; + cpu_state <= cpu_state_ld_rs1; + end + end + end + + cpu_state_ld_rs1: begin + reg_op1 <= 'bx; + reg_op2 <= 'bx; + + (* parallel_case *) + case (1'b1) + (CATCH_ILLINSN || WITH_PCPI) && instr_trap: begin + if (WITH_PCPI) begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + pcpi_valid <= 1; + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end else begin + cpu_state <= cpu_state_ld_rs2; + end + end else begin + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + ENABLE_COUNTERS && is_rdcycle_rdcycleh_rdinstr_rdinstrh: begin + (* parallel_case, full_case *) + case (1'b1) + instr_rdcycle: + reg_out <= count_cycle[31:0]; + instr_rdcycleh && ENABLE_COUNTERS64: + reg_out <= count_cycle[63:32]; + instr_rdinstr: + reg_out <= count_instr[31:0]; + instr_rdinstrh && ENABLE_COUNTERS64: + reg_out <= count_instr[63:32]; + endcase + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + is_lui_auipc_jal: begin + reg_op1 <= instr_lui ? 0 : reg_pc; + reg_op2 <= decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_getq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_setq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_rd <= latched_rd | irqregs_offset; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_retirq: begin + eoi <= 0; + irq_active <= 0; + latched_branch <= 1; + latched_store <= 1; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= CATCH_MISALIGN ? (cpuregs_rs1 & 32'h fffffffe) : cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_maskirq: begin + latched_store <= 1; + reg_out <= irq_mask; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + irq_mask <= cpuregs_rs1 | MASKED_IRQ; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_TIMER && instr_timer: begin + latched_store <= 1; + reg_out <= timer; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + timer <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + is_lb_lh_lw_lbu_lhu && !instr_trap: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_ldmem; + mem_do_rinst <= 1; + end + is_slli_srli_srai && !BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_sh <= decoded_rs2; + cpu_state <= cpu_state_shift; + end + is_jalr_addi_slti_sltiu_xori_ori_andi, is_slli_srli_srai && BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_op2 <= is_slli_srli_srai && BARREL_SHIFTER ? decoded_rs2 : decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + default: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + (* parallel_case *) + case (1'b1) + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end else + cpu_state <= cpu_state_ld_rs2; + end + endcase + end + + cpu_state_ld_rs2: begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + + (* parallel_case *) + case (1'b1) + WITH_PCPI && instr_trap: begin + pcpi_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end + + cpu_state_exec: begin + reg_out <= reg_pc + decoded_imm; + if ((TWO_CYCLE_ALU || TWO_CYCLE_COMPARE) && (alu_wait || alu_wait_2)) begin + mem_do_rinst <= mem_do_prefetch && !alu_wait_2; + alu_wait <= alu_wait_2; + end else + if (is_beq_bne_blt_bge_bltu_bgeu) begin + latched_rd <= 0; + latched_store <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + latched_branch <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + if (mem_done) + cpu_state <= cpu_state_fetch; + if (TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0) begin + decoder_trigger <= 0; + set_mem_do_rinst = 1; + end + end else begin + latched_branch <= instr_jalr; + latched_store <= 1; + latched_stalu <= 1; + cpu_state <= cpu_state_fetch; + end + end + + cpu_state_shift: begin + latched_store <= 1; + if (reg_sh == 0) begin + reg_out <= reg_op1; + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_fetch; + end else if (TWO_STAGE_SHIFT && reg_sh >= 4) begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 4; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 4; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 4; + endcase + reg_sh <= reg_sh - 4; + end else begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 1; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 1; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 1; + endcase + reg_sh <= reg_sh - 1; + end + end + + cpu_state_stmem: begin + if (ENABLE_TRACE) + reg_out <= reg_op2; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_wdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_sb: mem_wordsize <= 2; + instr_sh: mem_wordsize <= 1; + instr_sw: mem_wordsize <= 0; + endcase + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_wdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + cpu_state <= cpu_state_fetch; + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + end + end + end + + cpu_state_ldmem: begin + latched_store <= 1; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_rdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_lb || instr_lbu: mem_wordsize <= 2; + instr_lh || instr_lhu: mem_wordsize <= 1; + instr_lw: mem_wordsize <= 0; + endcase + latched_is_lu <= is_lbu_lhu_lw; + latched_is_lh <= instr_lh; + latched_is_lb <= instr_lb; + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_rdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + (* parallel_case, full_case *) + case (1'b1) + latched_is_lu: reg_out <= mem_rdata_word; + latched_is_lh: reg_out <= $signed(mem_rdata_word[15:0]); + latched_is_lb: reg_out <= $signed(mem_rdata_word[7:0]); + endcase + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + cpu_state <= cpu_state_fetch; + end + end + end + endcase + + if (CATCH_MISALIGN && resetn && (mem_do_rdata || mem_do_wdata)) begin + if (mem_wordsize == 0 && reg_op1[1:0] != 0) begin + `debug($display("MISALIGNED WORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (mem_wordsize == 1 && reg_op1[0] != 0) begin + `debug($display("MISALIGNED HALFWORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + end + if (CATCH_MISALIGN && resetn && mem_do_rinst && (COMPRESSED_ISA ? reg_pc[0] : |reg_pc[1:0])) begin + `debug($display("MISALIGNED INSTRUCTION: 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (!CATCH_ILLINSN && decoder_trigger_q && !decoder_pseudo_trigger_q && instr_ecall_ebreak) begin + cpu_state <= cpu_state_trap; + end + + if (!resetn || mem_done) begin + mem_do_prefetch <= 0; + mem_do_rinst <= 0; + mem_do_rdata <= 0; + mem_do_wdata <= 0; + end + + if (set_mem_do_rinst) + mem_do_rinst <= 1; + if (set_mem_do_rdata) + mem_do_rdata <= 1; + if (set_mem_do_wdata) + mem_do_wdata <= 1; + + irq_pending <= next_irq_pending & ~MASKED_IRQ; + + if (!CATCH_MISALIGN) begin + if (COMPRESSED_ISA) begin + reg_pc[0] <= 0; + reg_next_pc[0] <= 0; + end else begin + reg_pc[1:0] <= 0; + reg_next_pc[1:0] <= 0; + end + end + current_pc = 'bx; + end + +`ifdef RISCV_FORMAL + reg dbg_irq_call; + reg dbg_irq_enter; + reg [31:0] dbg_irq_ret; + always @(posedge clk) begin + rvfi_valid <= resetn && (launch_next_insn || trap) && dbg_valid_insn; + rvfi_order <= resetn ? rvfi_order + rvfi_valid : 0; + + rvfi_insn <= dbg_insn_opcode; + rvfi_rs1_addr <= dbg_rs1val_valid ? dbg_insn_rs1 : 0; + rvfi_rs2_addr <= dbg_rs2val_valid ? dbg_insn_rs2 : 0; + rvfi_pc_rdata <= dbg_insn_addr; + rvfi_rs1_rdata <= dbg_rs1val_valid ? dbg_rs1val : 0; + rvfi_rs2_rdata <= dbg_rs2val_valid ? dbg_rs2val : 0; + rvfi_trap <= trap; + rvfi_halt <= trap; + rvfi_intr <= dbg_irq_enter; + rvfi_mode <= 3; + + if (!resetn) begin + dbg_irq_call <= 0; + dbg_irq_enter <= 0; + end else + if (rvfi_valid) begin + dbg_irq_call <= 0; + dbg_irq_enter <= dbg_irq_call; + end else + if (irq_state == 1) begin + dbg_irq_call <= 1; + dbg_irq_ret <= next_pc; + end + + if (!resetn) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end else + if (cpuregs_write && !irq_state) begin + rvfi_rd_addr <= latched_rd; + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata : 0; + end else + if (rvfi_valid) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + + casez (dbg_insn_opcode) + 32'b 0000000_?????_000??_???_?????_0001011: begin // getq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + 32'b 0000001_?????_?????_???_000??_0001011: begin // setq + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + 32'b 0000010_?????_00000_???_00000_0001011: begin // retirq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + endcase + + if (!dbg_irq_call) begin + if (dbg_mem_instr) begin + rvfi_mem_addr <= 0; + rvfi_mem_rmask <= 0; + rvfi_mem_wmask <= 0; + rvfi_mem_rdata <= 0; + rvfi_mem_wdata <= 0; + end else + if (dbg_mem_valid && dbg_mem_ready) begin + rvfi_mem_addr <= dbg_mem_addr; + rvfi_mem_rmask <= dbg_mem_wstrb ? 0 : ~0; + rvfi_mem_wmask <= dbg_mem_wstrb; + rvfi_mem_rdata <= dbg_mem_rdata; + rvfi_mem_wdata <= dbg_mem_wdata; + end + end + end + + always @* begin + rvfi_pc_wdata = dbg_irq_call ? dbg_irq_ret : dbg_insn_addr; + end +`endif + + // Formal Verification +`ifdef FORMAL + reg [3:0] last_mem_nowait; + always @(posedge clk) + last_mem_nowait <= {last_mem_nowait, mem_ready || !mem_valid}; + + // stall the memory interface for max 4 cycles + restrict property (|last_mem_nowait || mem_ready || !mem_valid); + + // resetn low in first cycle, after that resetn high + restrict property (resetn != $initstate); + + // this just makes it much easier to read traces. uncomment as needed. + // assume property (mem_valid || !mem_ready); + + reg ok; + always @* begin + if (resetn) begin + // instruction fetches are read-only + if (mem_valid && mem_instr) + assert (mem_wstrb == 0); + + // cpu_state must be valid + ok = 0; + if (cpu_state == cpu_state_trap) ok = 1; + if (cpu_state == cpu_state_fetch) ok = 1; + if (cpu_state == cpu_state_ld_rs1) ok = 1; + if (cpu_state == cpu_state_ld_rs2) ok = !ENABLE_REGS_DUALPORT; + if (cpu_state == cpu_state_exec) ok = 1; + if (cpu_state == cpu_state_shift) ok = 1; + if (cpu_state == cpu_state_stmem) ok = 1; + if (cpu_state == cpu_state_ldmem) ok = 1; + assert (ok); + end + end + + reg last_mem_la_read = 0; + reg last_mem_la_write = 0; + reg [31:0] last_mem_la_addr; + reg [31:0] last_mem_la_wdata; + reg [3:0] last_mem_la_wstrb = 0; + + always @(posedge clk) begin + last_mem_la_read <= mem_la_read; + last_mem_la_write <= mem_la_write; + last_mem_la_addr <= mem_la_addr; + last_mem_la_wdata <= mem_la_wdata; + last_mem_la_wstrb <= mem_la_wstrb; + + if (last_mem_la_read) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wstrb == 0); + end + if (last_mem_la_write) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wdata == last_mem_la_wdata); + assert(mem_wstrb == last_mem_la_wstrb); + end + if (mem_la_read || mem_la_write) begin + assert(!mem_valid || mem_ready); + end + end +`endif +endmodule + +// This is a simple example implementation of PICORV32_REGS. +// Use the PICORV32_REGS mechanism if you want to use custom +// memory resources to implement the processor register file. +// Note that your implementation must match the requirements of +// the PicoRV32 configuration. (e.g. QREGS, etc) +module picorv32_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:30]; + + always @(posedge clk) + if (wen) regs[~waddr[4:0]] <= wdata; + + assign rdata1 = regs[~raddr1[4:0]]; + assign rdata2 = regs[~raddr2[4:0]]; +endmodule + + +/*************************************************************** + * picorv32_pcpi_mul + ***************************************************************/ + +module picorv32_pcpi_mul #( + parameter STEPS_AT_ONCE = 1, + parameter CARRY_CHAIN = 4 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg pcpi_wait_q; + wire mul_start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_mul <= 0; + instr_mulh <= 0; + instr_mulhsu <= 0; + instr_mulhu <= 0; + + if (resetn && pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul <= 1; + 3'b001: instr_mulh <= 1; + 3'b010: instr_mulhsu <= 1; + 3'b011: instr_mulhu <= 1; + endcase + end + + pcpi_wait <= instr_any_mul; + pcpi_wait_q <= pcpi_wait; + end + + reg [63:0] rs1, rs2, rd, rdx; + reg [63:0] next_rs1, next_rs2, this_rs2; + reg [63:0] next_rd, next_rdx, next_rdt; + reg [6:0] mul_counter; + reg mul_waiting; + reg mul_finish; + integer i, j; + + // carry save accumulator + always @* begin + next_rd = rd; + next_rdx = rdx; + next_rs1 = rs1; + next_rs2 = rs2; + + for (i = 0; i < STEPS_AT_ONCE; i=i+1) begin + this_rs2 = next_rs1[0] ? next_rs2 : 0; + if (CARRY_CHAIN == 0) begin + next_rdt = next_rd ^ next_rdx ^ this_rs2; + next_rdx = ((next_rd & next_rdx) | (next_rd & this_rs2) | (next_rdx & this_rs2)) << 1; + next_rd = next_rdt; + end else begin + next_rdt = 0; + for (j = 0; j < 64; j = j + CARRY_CHAIN) + {next_rdt[j+CARRY_CHAIN-1], next_rd[j +: CARRY_CHAIN]} = + next_rd[j +: CARRY_CHAIN] + next_rdx[j +: CARRY_CHAIN] + this_rs2[j +: CARRY_CHAIN]; + next_rdx = next_rdt << 1; + end + next_rs1 = next_rs1 >> 1; + next_rs2 = next_rs2 << 1; + end + end + + always @(posedge clk) begin + mul_finish <= 0; + if (!resetn) begin + mul_waiting <= 1; + end else + if (mul_waiting) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + + rd <= 0; + rdx <= 0; + mul_counter <= (instr_any_mulh ? 63 - STEPS_AT_ONCE : 31 - STEPS_AT_ONCE); + mul_waiting <= !mul_start; + end else begin + rd <= next_rd; + rdx <= next_rdx; + rs1 <= next_rs1; + rs2 <= next_rs2; + + mul_counter <= mul_counter - STEPS_AT_ONCE; + if (mul_counter[6]) begin + mul_finish <= 1; + mul_waiting <= 1; + end + end + end + + always @(posedge clk) begin + pcpi_wr <= 0; + pcpi_ready <= 0; + if (mul_finish && resetn) begin + pcpi_wr <= 1; + pcpi_ready <= 1; + pcpi_rd <= instr_any_mulh ? rd >> 32 : rd; + end + end +endmodule + +module picorv32_pcpi_fast_mul #( + parameter EXTRA_MUL_FFS = 0, + parameter EXTRA_INSN_FFS = 0, + parameter MUL_CLKGATE = 0 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output pcpi_wr, + output [31:0] pcpi_rd, + output pcpi_wait, + output pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg shift_out; + reg [3:0] active; + reg [32:0] rs1, rs2, rs1_q, rs2_q; + reg [63:0] rd, rd_q; + + wire pcpi_insn_valid = pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001; + reg pcpi_insn_valid_q; + + always @* begin + instr_mul = 0; + instr_mulh = 0; + instr_mulhsu = 0; + instr_mulhu = 0; + + if (resetn && (EXTRA_INSN_FFS ? pcpi_insn_valid_q : pcpi_insn_valid)) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul = 1; + 3'b001: instr_mulh = 1; + 3'b010: instr_mulhsu = 1; + 3'b011: instr_mulhu = 1; + endcase + end + end + + always @(posedge clk) begin + pcpi_insn_valid_q <= pcpi_insn_valid; + if (!MUL_CLKGATE || active[0]) begin + rs1_q <= rs1; + rs2_q <= rs2; + end + if (!MUL_CLKGATE || active[1]) begin + rd <= $signed(EXTRA_MUL_FFS ? rs1_q : rs1) * $signed(EXTRA_MUL_FFS ? rs2_q : rs2); + end + if (!MUL_CLKGATE || active[2]) begin + rd_q <= rd; + end + end + + always @(posedge clk) begin + if (instr_any_mul && !(EXTRA_MUL_FFS ? active[3:0] : active[1:0])) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + active[0] <= 1; + end else begin + active[0] <= 0; + end + + active[3:1] <= active; + shift_out <= instr_any_mulh; + + if (!resetn) + active <= 0; + end + + assign pcpi_wr = active[EXTRA_MUL_FFS ? 3 : 1]; + assign pcpi_wait = 0; + assign pcpi_ready = active[EXTRA_MUL_FFS ? 3 : 1]; +`ifdef RISCV_FORMAL_ALTOPS + assign pcpi_rd = + instr_mul ? (pcpi_rs1 + pcpi_rs2) ^ 32'h5876063e : + instr_mulh ? (pcpi_rs1 + pcpi_rs2) ^ 32'hf6583fb7 : + instr_mulhsu ? (pcpi_rs1 - pcpi_rs2) ^ 32'hecfbe137 : + instr_mulhu ? (pcpi_rs1 + pcpi_rs2) ^ 32'h949ce5e8 : 1'bx; +`else + assign pcpi_rd = shift_out ? (EXTRA_MUL_FFS ? rd_q : rd) >> 32 : (EXTRA_MUL_FFS ? rd_q : rd); +`endif +endmodule + + +/*************************************************************** + * picorv32_pcpi_div + ***************************************************************/ + +module picorv32_pcpi_div ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_div, instr_divu, instr_rem, instr_remu; + wire instr_any_div_rem = |{instr_div, instr_divu, instr_rem, instr_remu}; + + reg pcpi_wait_q; + wire start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_div <= 0; + instr_divu <= 0; + instr_rem <= 0; + instr_remu <= 0; + + if (resetn && pcpi_valid && !pcpi_ready && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b100: instr_div <= 1; + 3'b101: instr_divu <= 1; + 3'b110: instr_rem <= 1; + 3'b111: instr_remu <= 1; + endcase + end + + pcpi_wait <= instr_any_div_rem && resetn; + pcpi_wait_q <= pcpi_wait && resetn; + end + + reg [31:0] dividend; + reg [62:0] divisor; + reg [31:0] quotient; + reg [31:0] quotient_msk; + reg running; + reg outsign; + + always @(posedge clk) begin + pcpi_ready <= 0; + pcpi_wr <= 0; + pcpi_rd <= 'bx; + + if (!resetn) begin + running <= 0; + end else + if (start) begin + running <= 1; + dividend <= (instr_div || instr_rem) && pcpi_rs1[31] ? -pcpi_rs1 : pcpi_rs1; + divisor <= ((instr_div || instr_rem) && pcpi_rs2[31] ? -pcpi_rs2 : pcpi_rs2) << 31; + outsign <= (instr_div && (pcpi_rs1[31] != pcpi_rs2[31]) && |pcpi_rs2) || (instr_rem && pcpi_rs1[31]); + quotient <= 0; + quotient_msk <= 1 << 31; + end else + if (!quotient_msk && running) begin + running <= 0; + pcpi_ready <= 1; + pcpi_wr <= 1; +`ifdef RISCV_FORMAL_ALTOPS + case (1) + instr_div: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h7f8529ec; + instr_divu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h10e8fd70; + instr_rem: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h8da68fa5; + instr_remu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h3138d0e1; + endcase +`else + if (instr_div || instr_divu) + pcpi_rd <= outsign ? -quotient : quotient; + else + pcpi_rd <= outsign ? -dividend : dividend; +`endif + end else begin + if (divisor <= dividend) begin + dividend <= dividend - divisor; + quotient <= quotient | quotient_msk; + end + divisor <= divisor >> 1; +`ifdef RISCV_FORMAL_ALTOPS + quotient_msk <= quotient_msk >> 5; +`else + quotient_msk <= quotient_msk >> 1; +`endif + end + end +endmodule + + +/*************************************************************** + * picorv32_axi + ***************************************************************/ + +module picorv32_axi #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output trap, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + wire mem_instr; + wire mem_ready; + wire [31:0] mem_rdata; + + picorv32_axi_adapter axi_adapter ( + .clk (clk ), + .resetn (resetn ), + .mem_axi_awvalid(mem_axi_awvalid), + .mem_axi_awready(mem_axi_awready), + .mem_axi_awaddr (mem_axi_awaddr ), + .mem_axi_awprot (mem_axi_awprot ), + .mem_axi_wvalid (mem_axi_wvalid ), + .mem_axi_wready (mem_axi_wready ), + .mem_axi_wdata (mem_axi_wdata ), + .mem_axi_wstrb (mem_axi_wstrb ), + .mem_axi_bvalid (mem_axi_bvalid ), + .mem_axi_bready (mem_axi_bready ), + .mem_axi_arvalid(mem_axi_arvalid), + .mem_axi_arready(mem_axi_arready), + .mem_axi_araddr (mem_axi_araddr ), + .mem_axi_arprot (mem_axi_arprot ), + .mem_axi_rvalid (mem_axi_rvalid ), + .mem_axi_rready (mem_axi_rready ), + .mem_axi_rdata (mem_axi_rdata ), + .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 ) + ); + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); +endmodule + + +/*************************************************************** + * picorv32_axi_adapter + ***************************************************************/ + +module picorv32_axi_adapter ( + input clk, resetn, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Native PicoRV32 memory interface + + input mem_valid, + input mem_instr, + output mem_ready, + input [31:0] mem_addr, + input [31:0] mem_wdata, + input [ 3:0] mem_wstrb, + output [31:0] mem_rdata +); + reg ack_awvalid; + reg ack_arvalid; + reg ack_wvalid; + reg xfer_done; + + assign mem_axi_awvalid = mem_valid && |mem_wstrb && !ack_awvalid; + assign mem_axi_awaddr = mem_addr; + assign mem_axi_awprot = 0; + + assign mem_axi_arvalid = mem_valid && !mem_wstrb && !ack_arvalid; + assign mem_axi_araddr = mem_addr; + assign mem_axi_arprot = mem_instr ? 3'b100 : 3'b000; + + assign mem_axi_wvalid = mem_valid && |mem_wstrb && !ack_wvalid; + assign mem_axi_wdata = mem_wdata; + assign mem_axi_wstrb = mem_wstrb; + + assign mem_ready = mem_axi_bvalid || mem_axi_rvalid; + assign mem_axi_bready = mem_valid && |mem_wstrb; + assign mem_axi_rready = mem_valid && !mem_wstrb; + assign mem_rdata = mem_axi_rdata; + + always @(posedge clk) begin + if (!resetn) begin + ack_awvalid <= 0; + end else begin + xfer_done <= mem_valid && mem_ready; + if (mem_axi_awready && mem_axi_awvalid) + ack_awvalid <= 1; + if (mem_axi_arready && mem_axi_arvalid) + ack_arvalid <= 1; + if (mem_axi_wready && mem_axi_wvalid) + ack_wvalid <= 1; + if (xfer_done || !mem_valid) begin + ack_awvalid <= 0; + ack_arvalid <= 0; + ack_wvalid <= 0; + end + end + end +endmodule + + +/*************************************************************** + * picorv32_wb + ***************************************************************/ + +module picorv32_wb #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + output trap, + + // Wishbone interfaces + input wb_rst_i, + input wb_clk_i, + + output reg [31:0] wbm_adr_o, + output reg [31:0] wbm_dat_o, + input [31:0] wbm_dat_i, + output reg wbm_we_o, + output reg [3:0] wbm_sel_o, + output reg wbm_stb_o, + input wbm_ack_i, + output reg wbm_cyc_o, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data, + + output mem_instr +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + reg mem_ready; + reg [31:0] mem_rdata; + + wire clk; + wire resetn; + + assign clk = wb_clk_i; + assign resetn = ~wb_rst_i; + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); + + localparam IDLE = 2'b00; + localparam WBSTART = 2'b01; + localparam WBEND = 2'b10; + + reg [1:0] state; + + wire we; + assign we = (mem_wstrb[0] | mem_wstrb[1] | mem_wstrb[2] | mem_wstrb[3]); + + always @(posedge wb_clk_i) begin + if (wb_rst_i) begin + wbm_adr_o <= 0; + wbm_dat_o <= 0; + wbm_we_o <= 0; + wbm_sel_o <= 0; + wbm_stb_o <= 0; + wbm_cyc_o <= 0; + state <= IDLE; + end else begin + case (state) + IDLE: begin + if (mem_valid) begin + wbm_adr_o <= mem_addr; + wbm_dat_o <= mem_wdata; + wbm_we_o <= we; + wbm_sel_o <= mem_wstrb; + + wbm_stb_o <= 1'b1; + wbm_cyc_o <= 1'b1; + state <= WBSTART; + end else begin + mem_ready <= 1'b0; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBSTART:begin + if (wbm_ack_i) begin + mem_rdata <= wbm_dat_i; + mem_ready <= 1'b1; + + state <= WBEND; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBEND: begin + mem_ready <= 1'b0; + + state <= IDLE; + end + default: + state <= IDLE; + endcase + end + end +endmodule diff --git a/ice40/smoketest/attosoc/smoketest.sh b/ice40/smoketest/attosoc/smoketest.sh new file mode 100755 index 00000000..05408ab1 --- /dev/null +++ b/ice40/smoketest/attosoc/smoketest.sh @@ -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 diff --git a/json/jsonparse.cc b/json/jsonparse.cc index bddbee0b..5f4df0a2 100644 --- a/json/jsonparse.cc +++ b/json/jsonparse.cc @@ -594,7 +594,11 @@ void json_import_cell(Context *ctx, string modname, const std::vector 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 netnames; + std::vector 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 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); diff --git a/xc7/125MHz_to_60MHz.v b/xc7/125MHz_to_60MHz.v new file mode 100755 index 00000000..72e474d8 --- /dev/null +++ b/xc7/125MHz_to_60MHz.v @@ -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 diff --git a/xc7/arch.cc b/xc7/arch.cc index 4e02227f..8bb4ca4b 100644 --- a/xc7/arch.cc +++ b/xc7/arch.cc @@ -28,27 +28,8 @@ #include "router1.h" #include "util.h" -#include -#include -#include -#include -#include -#include -#include -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 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(what.str(1)); + const auto y = boost::lexical_cast(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(what.str(1)); - const auto y = boost::lexical_cast(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(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> delay_lookup; + std::unordered_map 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).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_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).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).expandSegmentSinks(currentTilewire, arcs, DDB::eExpandDirectionNone, + // false /* inUseTied */, true /*inUseRegular */, + // true /* inUseIrregular */, !clb /* inUseRoutethrough */); + { + // expand the segment + TilewireVector segment; + const_cast(*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).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(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(new TorcInfo(this, "xc7z020", args.package)); + } else if (args.type == ArchArgs::VX980) { + torc_info = std::unique_ptr(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 Arch::getBelPins(BelId bel) const { std::vector 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 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> Arch::getWireAttrs(WireId wire) const { std::vector> 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> Arch::getPipAttrs(PipId pip) const { std::vector> 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 Arch::getGroups() const { std::vector 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 Arch::getGroupWires(GroupId group) const std::vector Arch::getGroupPips(GroupId group) const { std::vector ret; + NPNR_ASSERT("TODO"); return ret; } std::vector Arch::getGroupGroups(GroupId group) const { std::vector 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 -inline void load_construct_data( - Archive & ar, NEXTPNR_NAMESPACE::TorcInfo * t, const unsigned int file_version -){ - const auto& inDeviceName = static_cast(ar).inDeviceName; - const auto& inPackageName = static_cast(ar).inPackageName; - ::new(t)NEXTPNR_NAMESPACE::TorcInfo(inDeviceName, inPackageName); -} - -template -void save(Archive& ar, const Segments::SegmentReference& o, unsigned int) { - ar & o.getCompactSegmentIndex(); - ar & o.getAnchorTileIndex(); -} -template -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 \ -void save(Archive& ar, const __T__& o, unsigned int) { \ - ar & static_cast<__T__::pod>(o); \ -} \ -template \ -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 -void save(Archive& ar, const Arc& o, unsigned int) { - ar & o.getSourceTilewire(); - ar & o.getSinkTilewire(); -} -template -void load(Archive& ar, Arc& o, unsigned int) { - Tilewire s, t; - ar & s; - ar & t; - o = Arc(s, t); -} -template -void save(Archive& ar, const Tilewire& o, unsigned int) { - ar & o.getTileIndex(); - ar & o.getWireIndex(); -} -template -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 -void serialize(Archive& ar, NEXTPNR_NAMESPACE::Loc& o, unsigned int) { - ar & o.x; - ar & o.y; - ar & o.z; -} -template -void serialize(Archive& ar, NEXTPNR_NAMESPACE::DelayInfo& o, unsigned int) { - ar & o.delay; -} -template -void save(Archive& ar, const NEXTPNR_NAMESPACE::IdString& o, unsigned int) { - const std::string i = o.str(static_cast(ar).ctx); - ar & i; -} -template -void load(Archive& ar, NEXTPNR_NAMESPACE::IdString& o, unsigned int) { - std::string i; - ar & i; - o = static_cast(ar).ctx->id(i); -} -#define SERIALIZE_INDEX(__T__) \ -template \ -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 diff --git a/xc7/arch.h b/xc7/arch.h index 238ff18b..699d357e 100644 --- a/xc7/arch.h +++ b/xc7/arch.h @@ -21,8 +21,6 @@ #error Include "arch.h" via "nextpnr.h" only. #endif -#include - #include "torc/Architecture.hpp" #include "torc/Common.hpp" using namespace torc::architecture; @@ -332,33 +330,12 @@ struct TorcInfo std::vector> wire_to_pips_downhill; std::vector pip_to_arc; int num_pips; - std::vector pip_to_dst_wire; int width; int height; + std::vector wire_is_global; + std::vector> 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 friend inline void load_construct_data(Archive &ar, T *t, const unsigned int file_version); - template - 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 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 diff --git a/xc7/arch_place.cc b/xc7/arch_place.cc index fd09006e..7d1e32e4 100644 --- a/xc7/arch_place.cc +++ b/xc7/arch_place.cc @@ -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 bel_cells; + if (getBelType(bel) == id("XC7_LC")) { + std::array 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 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 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 diff --git a/xc7/attosoc.pcf b/xc7/attosoc.pcf new file mode 100644 index 00000000..3ef75a17 --- /dev/null +++ b/xc7/attosoc.pcf @@ -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; diff --git a/xc7/attosoc.sh b/xc7/attosoc.sh new file mode 100755 index 00000000..e7f1c95a --- /dev/null +++ b/xc7/attosoc.sh @@ -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 diff --git a/xc7/attosoc.v b/xc7/attosoc.v new file mode 100644 index 00000000..4921e298 --- /dev/null +++ b/xc7/attosoc.v @@ -0,0 +1,127 @@ +/* + * ECP5 PicoRV32 demo + * + * Copyright (C) 2017 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * 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 diff --git a/xc7/attosoc.ys b/xc7/attosoc.ys new file mode 100644 index 00000000..105728c9 --- /dev/null +++ b/xc7/attosoc.ys @@ -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 + +write_json attosoc.json diff --git a/xc7/attosoc_tb.vhd b/xc7/attosoc_tb.vhd new file mode 100644 index 00000000..fe697c3f --- /dev/null +++ b/xc7/attosoc_tb.vhd @@ -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; diff --git a/xc7/attosoc_top.v b/xc7/attosoc_top.v new file mode 100644 index 00000000..b794abda --- /dev/null +++ b/xc7/attosoc_top.v @@ -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 + diff --git a/xc7/blinky.pcf b/xc7/blinky.pcf index 6c2e5149..1b85ac5f 100644 --- a/xc7/blinky.pcf +++ b/xc7/blinky.pcf @@ -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; diff --git a/xc7/cells.cc b/xc7/cells.cc index f8eb1373..07cb30b0 100644 --- a/xc7/cells.cc +++ b/xc7/cells.cc @@ -48,8 +48,6 @@ std::unique_ptr 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 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 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; } diff --git a/xc7/constids.inc b/xc7/constids.inc index bf0e7114..7fae17e6 100644 --- a/xc7/constids.inc +++ b/xc7/constids.inc @@ -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) diff --git a/xc7/delay.cc b/xc7/delay.cc index 0df02f7e..67f96b98 100644 --- a/xc7/delay.cc +++ b/xc7/delay.cc @@ -25,106 +25,38 @@ NEXTPNR_NAMESPACE_BEGIN #define NUM_FUZZ_ROUTES 100000 -void ice40DelayFuzzerMain(Context *ctx) -{ - // std::vector 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 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 diff --git a/xc7/firmware.hex b/xc7/firmware.hex new file mode 100644 index 00000000..ef9aa3c5 --- /dev/null +++ b/xc7/firmware.hex @@ -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 diff --git a/xc7/main.cc b/xc7/main.cc index 65fabff4..cac9a397 100644 --- a/xc7/main.cc +++ b/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(), "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(), "set device package"); specific.add_options()("pcf", po::value(), "PCF constraints file to ingest"); specific.add_options()("xdl", po::value(), "XDL file to write"); // specific.add_options()("tmfuzz", "run path delay estimate fuzzer"); @@ -97,6 +98,12 @@ std::unique_ptr 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"; diff --git a/xc7/pack.cc b/xc7/pack.cc index d2130e72..a4b57f2a 100644 --- a/xc7/pack.cc +++ b/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 exhausted_cells; - std::unordered_set packed_cells; - std::vector> 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 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 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 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 packed_cells; - std::vector> new_cells; - - for (auto cell : sorted(ctx->cells)) { - CellInfo *ci = cell.second; - if (is_ram(ctx, ci)) { - std::unique_ptr 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 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 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 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 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 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 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 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"; } } diff --git a/xc7/picorv32.pcf b/xc7/picorv32.pcf index 499b965d..0f023697 100644 --- a/xc7/picorv32.pcf +++ b/xc7/picorv32.pcf @@ -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; diff --git a/xc7/picorv32.ys b/xc7/picorv32.ys index 61916173..e6eec6cd 100644 --- a/xc7/picorv32.ys +++ b/xc7/picorv32.ys @@ -1,5 +1,6 @@ read_verilog picorv32.v read_verilog picorv32_top.v +read_verilog 125MHz_to_60MHz.v #synth_xilinx -top picorv32 diff --git a/xc7/picorv32_top.v b/xc7/picorv32_top.v index 7e2bf488..34149622 100644 --- a/xc7/picorv32_top.v +++ b/xc7/picorv32_top.v @@ -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), diff --git a/xc7/xdl.cc b/xc7/xdl.cc index 32546490..ef1be83e 100644 --- a/xc7/xdl.cc +++ b/xc7/xdl.cc @@ -25,6 +25,7 @@ #include "log.h" #include "nextpnr.h" #include "util.h" +#include #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 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(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); }