mistral: Add a basic QSF parser

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2021-05-09 12:07:24 +01:00
parent 595b354184
commit 96f16c8635
4 changed files with 295 additions and 1 deletions

View File

@ -455,6 +455,10 @@ struct Arch : BaseArch<ArchRanges>
typedef std::unordered_map<IdString, CellPinStyle> CellPinsData; // pins.cc
static const std::unordered_map<IdString, CellPinsData> 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<IdString, std::unordered_map<IdString, Property>> io_attr;
void read_qsf(std::istream &in); // qsf.cc
};
NEXTPNR_NAMESPACE_END

View File

@ -72,3 +72,5 @@ X(WIRE)
X(GND)
X(VCC)
X(LOC)

View File

@ -49,6 +49,7 @@ po::options_description MistralCommandHandler::getArchOptions()
po::options_description specific("Architecture specific options");
specific.add_options()("mistral", po::value<std::string>(), "path to mistral root");
specific.add_options()("device", po::value<std::string>(), "device name (e.g. 5CSEBA6U23I7)");
specific.add_options()("qsf", po::value<std::string>(), "path to QSF constraints file");
return specific;
}
@ -74,7 +75,13 @@ std::unique_ptr<Context> MistralCommandHandler::createContext(std::unordered_map
void MistralCommandHandler::customAfterLoad(Context *ctx)
{
// TODO: qsf parsing
if (vm.count("qsf")) {
std::string filename = vm["qsf"].as<std::string>();
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[])

281
mistral/qsf.cc Normal file
View File

@ -0,0 +1,281 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include <iterator>
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<std::string, std::vector<std::string>> option_map_t;
struct QsfCommand
{
std::string name; // name of the command
std::vector<QsfOption> options; // list of "-options"
int pos_arg_count; // number of positional arguments expected to follow the command, -1 for any
std::function<void(Context *ctx, const option_map_t &options, const std::vector<std::string> &pos_args)> func;
};
void set_location_assignment_cmd(Context *ctx, const option_map_t &options, const std::vector<std::string> &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<std::string> &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<std::string> &pos_args)
{
// TODO
}
static const std::vector<QsfCommand> 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<StringVal> get_arguments()
{
std::vector<StringVal> args;
while (!skip_check_eol()) {
args.push_back(get_str());
}
skip_blank(true);
return args;
}
void evaluate(const std::vector<StringVal> &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<std::string> 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<char>(in), {});
QsfParser(buf, getCtx())();
}
NEXTPNR_NAMESPACE_END