mistral: Add a basic QSF parser
Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
parent
595b354184
commit
96f16c8635
@ -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
|
||||
|
@ -72,3 +72,5 @@ X(WIRE)
|
||||
|
||||
X(GND)
|
||||
X(VCC)
|
||||
|
||||
X(LOC)
|
@ -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
281
mistral/qsf.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user