diff --git a/mistral/arch.h b/mistral/arch.h index d4f6bd5e..b818f001 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -455,6 +455,10 @@ struct Arch : BaseArch typedef std::unordered_map CellPinsData; // pins.cc static const std::unordered_map cell_pins_db; // pins.cc CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; // pins.cc + + // List of IO constraints, used by QSF parser + std::unordered_map> io_attr; + void read_qsf(std::istream &in); // qsf.cc }; NEXTPNR_NAMESPACE_END diff --git a/mistral/constids.inc b/mistral/constids.inc index c17d9f89..342ca353 100644 --- a/mistral/constids.inc +++ b/mistral/constids.inc @@ -72,3 +72,5 @@ X(WIRE) X(GND) X(VCC) + +X(LOC) \ No newline at end of file diff --git a/mistral/main.cc b/mistral/main.cc index 702b4b7e..d5816693 100644 --- a/mistral/main.cc +++ b/mistral/main.cc @@ -49,6 +49,7 @@ po::options_description MistralCommandHandler::getArchOptions() po::options_description specific("Architecture specific options"); specific.add_options()("mistral", po::value(), "path to mistral root"); specific.add_options()("device", po::value(), "device name (e.g. 5CSEBA6U23I7)"); + specific.add_options()("qsf", po::value(), "path to QSF constraints file"); return specific; } @@ -74,7 +75,13 @@ std::unique_ptr MistralCommandHandler::createContext(std::unordered_map void MistralCommandHandler::customAfterLoad(Context *ctx) { - // TODO: qsf parsing + if (vm.count("qsf")) { + std::string filename = vm["qsf"].as(); + std::ifstream in(filename); + if (!in) + log_error("Failed to open input QSF file %s.\n", filename.c_str()); + ctx->read_qsf(in); + } } int main(int argc, char *argv[]) diff --git a/mistral/qsf.cc b/mistral/qsf.cc new file mode 100644 index 00000000..3f201aa1 --- /dev/null +++ b/mistral/qsf.cc @@ -0,0 +1,281 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 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 "log.h" +#include "nextpnr.h" +#include "util.h" + +#include + +NEXTPNR_NAMESPACE_BEGIN + +namespace { + +struct QsfOption +{ + std::string name; // name, excluding the initial '-' + int arg_count; // number of arguments that follow the option + bool required; // error out if this option isn't passed +}; + +typedef std::unordered_map> option_map_t; + +struct QsfCommand +{ + std::string name; // name of the command + std::vector options; // list of "-options" + int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any + std::function &pos_args)> func; +}; + +void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); +} + +void set_instance_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + ctx->io_attr[ctx->id(options.at("to").at(0))][id_LOC] = pos_args.at(0); +} + +void set_global_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector &pos_args) +{ + // TODO +} + +static const std::vector commands = { + {"set_location_assignment", {{"to", 1, true}}, 1, set_location_assignment_cmd}, + {"set_instance_assignment", + {{"to", 1, true}, {"name", 1, true}, {"section_id", 1, false}}, + 1, + set_instance_assignment_cmd}, + {"set_global_assignment", + {{"name", 1, true}, {"section_id", 1, false}, {"rise", 0, false}, {"fall", 0, false}}, + 1, + set_global_assignment_cmd}, +}; + +struct QsfParser +{ + std::string buf; + int pos = 0; + int lineno = 0; + Context *ctx; + + QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; + + inline bool eof() const { return pos == int(buf.size()); } + + inline char peek() const { return buf.at(pos); } + + inline char get() + { + char c = buf.at(pos++); + if (c == '\n') + ++lineno; + return c; + } + + std::string get(int n) + { + std::string s = buf.substr(pos, n); + pos += n; + return s; + } + + // If next char matches c, take it from the stream and return true + bool check_get(char c) + { + if (peek() == c) { + get(); + return true; + } else { + return false; + } + } + + // If next char matches any in chars, take it from the stream and return true + bool check_get_any(const std::string &chrs) + { + char c = peek(); + if (chrs.find(c) != std::string::npos) { + get(); + return true; + } else { + return false; + } + } + + inline void skip_blank(bool nl = false) + { + while (!eof() && check_get_any(nl ? " \t\n\r" : " \t")) + ; + } + + // Return true if end of line (or file) + inline bool skip_check_eol() + { + skip_blank(false); + if (eof()) + return true; + char c = peek(); + // Comments count as end of line + if (c == '#') { + get(); + while (!eof() && peek() != '\n' && peek() != '\r') + get(); + return true; + } + if (c == ';') { + // Forced end of line + get(); + return true; + } + return (c == '\n' || c == '\r'); + } + + // We need to distinguish between quoted and unquoted strings, the former don't count as options + struct StringVal + { + std::string str; + bool is_quoted = false; + }; + + inline StringVal get_str() + { + StringVal s; + skip_blank(false); + if (eof()) + return {"", false}; + + bool in_quotes = false, in_braces = false, escaped = false; + + char c = get(); + + if (c == '"') { + in_quotes = true; + s.is_quoted = true; + } else if (c == '{') { + in_braces = true; + s.is_quoted = true; + } else { + s.str += c; + } + + while (!eof()) { + char c = peek(); + if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == '\n' || c == '\r')) { + break; + } + get(); + if (escaped) { + s.str += c; + escaped = false; + } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { + break; + } else if (c == '\\') { + escaped = true; + } else { + s.str += c; + } + } + + return s; + } + + std::vector get_arguments() + { + std::vector args; + while (!skip_check_eol()) { + args.push_back(get_str()); + } + skip_blank(true); + return args; + } + + void evaluate(const std::vector &args) + { + if (args.empty()) + return; + auto cmd_name = args.at(0).str; + auto fnd_cmd = + std::find_if(commands.begin(), commands.end(), [&](const QsfCommand &c) { return c.name == cmd_name; }); + if (fnd_cmd == commands.end()) { + log_warning("Ignoring unknown command '%s' (line %d)\n", cmd_name.c_str(), lineno); + return; + } + option_map_t opt; + std::vector pos_args; + for (size_t i = 1; i < args.size(); i++) { + auto arg = args.at(i); + if (arg.str.at(0) == '-' && !arg.is_quoted) { + for (auto &opt_data : fnd_cmd->options) { + if (arg.str.compare(1, std::string::npos, opt_data.name) != 0) + continue; + opt[opt_data.name]; // create empty entry, even if 0 arguments + for (int j = 0; j < opt_data.arg_count; j++) { + ++i; + if (i >= args.size()) + log_error("Unexpected end of argument list to option '%s' (line %d)\n", arg.str.c_str(), + lineno); + opt[opt_data.name].push_back(args.at(i).str); + } + goto done; + } + log_error("Unknown option '%s' to command '%s' (line %d)\n", arg.str.c_str(), cmd_name.c_str(), lineno); + done:; + } else { + // positional argument + pos_args.push_back(arg.str); + } + } + // Check positional argument count + if (int(pos_args.size()) != fnd_cmd->pos_arg_count && fnd_cmd->pos_arg_count != -1) { + log_error("Expected %d positional arguments to command '%s', got %d (line %d)\n", fnd_cmd->pos_arg_count, + cmd_name.c_str(), int(pos_args.size()), lineno); + } + // Check required options + for (auto &opt_data : fnd_cmd->options) { + if (opt_data.required && !opt.count(opt_data.name)) + log_error("Missing required option '%s' to command '%s' (line %d)\n", opt_data.name.c_str(), + cmd_name.c_str(), lineno); + } + // Execute + fnd_cmd->func(ctx, opt, pos_args); + } + + void operator()() + { + while (!eof()) { + skip_blank(true); + auto args = get_arguments(); + evaluate(args); + } + } +}; + +}; // namespace + +void Arch::read_qsf(std::istream &in) +{ + std::string buf(std::istreambuf_iterator(in), {}); + QsfParser(buf, getCtx())(); +} + +NEXTPNR_NAMESPACE_END \ No newline at end of file