From 05a191a01492fd954d1b9ef0a9db182abdd5a44b Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 1 Apr 2023 19:45:36 +0200 Subject: [PATCH] Added LPF support --- gui/machxo2/mainwindow.cc | 4 +- machxo2/arch.h | 3 + machxo2/lpf.cc | 186 ++++++++++++++++++++++++++++++++++++++ machxo2/main.cc | 36 +++++++- 4 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 machxo2/lpf.cc diff --git a/gui/machxo2/mainwindow.cc b/gui/machxo2/mainwindow.cc index 9b7cfb17..179bf8e1 100644 --- a/gui/machxo2/mainwindow.cc +++ b/gui/machxo2/mainwindow.cc @@ -124,7 +124,7 @@ void MainWindow::open_lpf() { QString fileName = QFileDialog::getOpenFileName(this, QString("Open LPF"), QString(), QString("*.lpf")); if (!fileName.isEmpty()) { - /*std::ifstream in(fileName.toStdString()); + std::ifstream in(fileName.toStdString()); if (ctx->apply_lpf(fileName.toStdString(), in)) { log("Loading LPF successful.\n"); actionPack->setEnabled(true); @@ -132,7 +132,7 @@ void MainWindow::open_lpf() } else { actionLoadLPF->setEnabled(true); log("Loading LPF failed.\n"); - }*/ + } } } diff --git a/machxo2/arch.h b/machxo2/arch.h index 16150168..e7614582 100644 --- a/machxo2/arch.h +++ b/machxo2/arch.h @@ -960,6 +960,9 @@ struct Arch : BaseArch NPNR_ASSERT_FALSE_STR("no tile with type " + type); } + // Apply LPF constraints to the context + bool apply_lpf(std::string filename, std::istream &in); + static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; diff --git a/machxo2/lpf.cc b/machxo2/lpf.cc new file mode 100644 index 00000000..2e34f54a --- /dev/null +++ b/machxo2/lpf.cc @@ -0,0 +1,186 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 gatecat + * + * 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 +#include + +#include "arch.h" +#include "log.h" +#include "nextpnr_namespaces.h" + +NEXTPNR_NAMESPACE_BEGIN + +static const pool sysconfig_keys = { + "SLAVE_SPI_PORT", "MASTER_SPI_PORT", "SLAVE_PARALLEL_PORT", + "BACKGROUND_RECONFIG", "DONE_EX", "DONE_OD", + "DONE_PULL", "MCCLK_FREQ", "TRANSFR", + "CONFIG_IOVOLTAGE", "CONFIG_SECURE", "WAKE_UP", + "COMPRESS_CONFIG", "CONFIG_MODE", "INBUF", +}; + +static const pool iobuf_keys = { + "IO_TYPE", "BANK", "BANK_VCC", "VREF", "PULLMODE", "DRIVE", "SLEWRATE", + "CLAMP", "OPENDRAIN", "DIFFRESISTOR", "DIFFDRIVE", "HYSTERESIS", "TERMINATION", +}; + +bool Arch::apply_lpf(std::string filename, std::istream &in) +{ + auto isempty = [](const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return isblank(c) || c == '\r' || c == '\n'; }); + }; + try { + if (!in) + log_error("failed to open LPF file\n"); + std::string line; + std::string linebuf; + int lineno = 0; + auto strip_quotes = [&](const std::string &str) { + if (str.at(0) == '"') { + if (str.back() != '"') { + log_error("expected '\"' at end of string '%s' (on line %d)\n", str.c_str(), lineno); + } + return str.substr(1, str.size() - 2); + } else { + return str; + } + }; + while (std::getline(in, line)) { + ++lineno; + size_t cstart = line.find('#'); + if (cstart != std::string::npos) + line = line.substr(0, cstart); + cstart = line.find("//"); + if (cstart != std::string::npos) + line = line.substr(0, cstart); + if (isempty(line)) + continue; + linebuf += line; + // Look for a command up to a semicolon + size_t scpos = linebuf.find(';'); + while (scpos != std::string::npos) { + std::string command = linebuf.substr(0, scpos); + // Split command into words + std::stringstream ss(command); + std::vector words; + std::string tmp; + while (ss >> tmp) + words.push_back(tmp); + if (words.size() > 0) { + std::string verb = words.at(0); + if (verb == "BLOCK") { + if (words.size() != 2 || (words.at(1) != "ASYNCPATHS" && words.at(1) != "RESETPATHS")) + log_warning(" ignoring unsupported LPF command '%s' (on line %d)\n", command.c_str(), + lineno); + } else if (verb == "SYSCONFIG") { + for (size_t i = 1; i < words.size(); i++) { + std::string setting = words.at(i); + size_t eqpos = setting.find('='); + if (eqpos == std::string::npos) + log_error("expected syntax 'SYSCONFIG =...' (on line %d)\n", lineno); + std::string key = setting.substr(0, eqpos), value = setting.substr(eqpos + 1); + if (!sysconfig_keys.count(key)) + log_error("unexpected SYSCONFIG key '%s' (on line %d)\n", key.c_str(), lineno); + settings[id("arch.sysconfig." + key)] = value; + } + } else if (verb == "FREQUENCY") { + if (words.size() < 2) + log_error("expected object type after FREQUENCY (on line %d)\n", lineno); + std::string etype = words.at(1); + if (etype == "PORT" || etype == "NET") { + if (words.size() < 4) + log_error("expected frequency value and unit after 'FREQUENCY %s' (on line %d)\n", + etype.c_str(), lineno); + std::string target = strip_quotes(words.at(2)); + float freq = std::stof(words.at(3)); + std::string unit = words.at(4); + boost::algorithm::to_upper(unit); + if (unit == "MHZ") + ; + else if (unit == "KHZ") + freq /= 1.0e3; + else if (unit == "HZ") + freq /= 1.0e6; + else + log_error("unsupported frequency unit '%s' (on line %d)\n", unit.c_str(), lineno); + addClock(id(target), freq); + } else { + log_warning(" ignoring unsupported LPF command '%s %s' (on line %d)\n", command.c_str(), + etype.c_str(), lineno); + } + } else if (verb == "LOCATE") { + if (words.size() < 5) + log_error("expected syntax 'LOCATE COMP SITE ' (on line %d)\n", lineno); + if (words.at(1) != "COMP") + log_error("expected 'COMP' after 'LOCATE' (on line %d)\n", lineno); + std::string cell = strip_quotes(words.at(2)); + if (words.at(3) != "SITE") + log_error("expected 'SITE' after 'LOCATE COMP %s' (on line %d)\n", cell.c_str(), lineno); + if (words.size() > 5) + log_error("unexpected input following LOCATE clause (on line %d)\n", lineno); + auto fnd_cell = cells.find(id(cell)); + // 1-bit wires are treated as scalar by nextpnr. + // In HDL they might have been a singleton vector. + if (fnd_cell == cells.end() && cell.size() >= 3 && cell.substr(cell.size() - 3) == "[0]") { + cell = cell.substr(0, cell.size() - 3); + fnd_cell = cells.find(id(cell)); + } + + if (fnd_cell != cells.end()) { + fnd_cell->second->attrs[id_LOC] = strip_quotes(words.at(4)); + } + } else if (verb == "IOBUF") { + if (words.size() < 3) + log_error("expected syntax 'IOBUF PORT =...' (on line %d)\n", + lineno); + if (words.at(1) != "PORT") + log_error("expected 'PORT' after 'IOBUF' (on line %d)\n", lineno); + std::string cell = strip_quotes(words.at(2)); + auto fnd_cell = cells.find(id(cell)); + if (fnd_cell != cells.end()) { + for (size_t i = 3; i < words.size(); i++) { + std::string setting = words.at(i); + size_t eqpos = setting.find('='); + if (eqpos == std::string::npos) + log_error( + "expected syntax 'IOBUF PORT =...' (on line %d)\n", + lineno); + std::string key = setting.substr(0, eqpos), value = setting.substr(eqpos + 1); + if (!iobuf_keys.count(key)) + log_warning("IOBUF '%s' attribute '%s' is not recognised (on line %d)\n", + cell.c_str(), key.c_str(), lineno); + fnd_cell->second->attrs[id(key)] = value; + } + } + } + } + + linebuf = linebuf.substr(scpos + 1); + scpos = linebuf.find(';'); + } + } + if (!isempty(linebuf)) + log_error("unexpected end of LPF file\n"); + settings[id("input/lpf")] = filename; + return true; + } catch (log_execution_error_exception) { + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/main.cc b/machxo2/main.cc index f21f8c3b..ff72ace3 100644 --- a/machxo2/main.cc +++ b/machxo2/main.cc @@ -36,6 +36,7 @@ class MachXO2CommandHandler : public CommandHandler virtual ~MachXO2CommandHandler(){}; std::unique_ptr createContext(dict &values) override; void setupArchContext(Context *ctx) override{}; + void customAfterLoad(Context *ctx) override; void customBitstream(Context *ctx) override; protected: @@ -50,7 +51,10 @@ po::options_description MachXO2CommandHandler::getArchOptions() specific.add_options()("device", po::value(), "device name"); specific.add_options()("list-devices", "list all supported device names"); specific.add_options()("textcfg", po::value(), "textual configuration in Trellis format to write"); - // specific.add_options()("lpf", po::value>(), "LPF pin constraint file(s)"); + + specific.add_options()("lpf", po::value>(), "LPF pin constraint file(s)"); + specific.add_options()("lpf-allow-unconstrained", "don't require LPF file(s) to constrain all IO"); + specific.add_options()("disable-router-lutperm", "don't allow the router to permute LUT inputs"); return specific; @@ -82,6 +86,36 @@ std::unique_ptr MachXO2CommandHandler::createContext(dict files = vm["lpf"].as>(); + for (const auto &filename : files) { + std::ifstream in(filename); + if (!in) + log_error("failed to open LPF file '%s'\n", filename.c_str()); + if (!ctx->apply_lpf(filename, in)) + log_error("failed to parse LPF file '%s'\n", filename.c_str()); + } + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_obuf") || + ci->type == ctx->id("$nextpnr_iobuf")) { + if (!ci->attrs.count(id_LOC)) { + if (vm.count("lpf-allow-unconstrained")) + log_warning("IO '%s' is unconstrained in LPF and will be automatically placed\n", + cell.first.c_str(ctx)); + else + log_error("IO '%s' is unconstrained in LPF (override this error with " + "--lpf-allow-unconstrained)\n", + cell.first.c_str(ctx)); + } + } + } + } +} + int main(int argc, char *argv[]) { MachXO2CommandHandler handler(argc, argv);