nextpnr/mistral/qsf.cc

282 lines
8.4 KiB
C++
Raw Normal View History

/*
* 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 dict<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))][ctx->id(options.at("name").at(0))] = 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