2021-05-09 19:07:24 +08:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
};
|
|
|
|
|
2021-06-02 17:01:36 +08:00
|
|
|
typedef dict<std::string, std::vector<std::string>> option_map_t;
|
2021-05-09 19:07:24 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2021-05-09 20:16:22 +08:00
|
|
|
ctx->io_attr[ctx->id(options.at("to").at(0))][ctx->id(options.at("name").at(0))] = pos_args.at(0);
|
2021-05-09 19:07:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2024-09-30 20:51:33 +08:00
|
|
|
QsfParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx) {};
|
2021-05-09 19:07:24 +08:00
|
|
|
|
|
|
|
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())();
|
|
|
|
}
|
|
|
|
|
2021-05-15 21:51:12 +08:00
|
|
|
NEXTPNR_NAMESPACE_END
|