From 0d5d32951c2f2100e436a4fbaf82d23e645ae4ff Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Mon, 12 Aug 2024 17:45:27 +0200 Subject: [PATCH] SDC parsing support (#1348) * kernel: Add SDC file parser * kernel: Add sdc as valid option * kernel/sdc: Add error on EOF when fetching strings * kernel/sdc: WIP command parsing for set_false_path * kernel/sdc: Fully parse set_false_path * kernel/sdc: Handle review comments --- common/kernel/command.cc | 15 ++ common/kernel/context.h | 4 + common/kernel/sdc.cc | 446 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 465 insertions(+) create mode 100644 common/kernel/sdc.cc diff --git a/common/kernel/command.cc b/common/kernel/command.cc index 9b8f8616b7..01c556b0e6 100644 --- a/common/kernel/command.cc +++ b/common/kernel/command.cc @@ -375,6 +375,7 @@ po::options_description CommandHandler::getGeneralOptions() general.add_options()("freq", po::value(), "set target frequency for design in MHz"); general.add_options()("timing-allow-fail", "allow timing to fail in design"); general.add_options()("no-tmdriv", "disable timing-driven placement"); + general.add_options()("sdc", po::value(), "Generic timing constraints SDC file to load"); general.add_options()("sdf", po::value(), "SDF delay back-annotation file to write"); general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator"); general.add_options()("no-print-critical-path-source", @@ -605,6 +606,13 @@ int CommandHandler::executeMain(std::unique_ptr ctx) std::ifstream f(filename); if (!parse_json(f, filename, w.getContext())) log_error("Loading design failed.\n"); + + if (vm.count("sdc")) { + std::string sdc_filename = vm["sdc"].as(); + std::ifstream sdc_stream(sdc_filename); + ctx->read_sdc(sdc_stream); + } + customAfterLoad(w.getContext()); w.notifyChangeContext(); w.updateActions(); @@ -613,6 +621,7 @@ int CommandHandler::executeMain(std::unique_ptr ctx) } catch (log_execution_error_exception) { // show error is handled by gui itself } + w.show(); return a.exec(); @@ -624,6 +633,12 @@ int CommandHandler::executeMain(std::unique_ptr ctx) if (!parse_json(f, filename, ctx.get())) log_error("Loading design failed.\n"); + if (vm.count("sdc")) { + std::string sdc_filename = vm["sdc"].as(); + std::ifstream sdc_stream(sdc_filename); + ctx->read_sdc(sdc_stream); + } + customAfterLoad(ctx.get()); } diff --git a/common/kernel/context.h b/common/kernel/context.h index 5543a2cf27..bd50e81791 100644 --- a/common/kernel/context.h +++ b/common/kernel/context.h @@ -105,6 +105,10 @@ struct Context : Arch, DeterministicRNG // provided by timing_log.cc void log_timing_results(TimingResult &result, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure); + + // provided by sdc.cc + void read_sdc(std::istream &in); + // -------------------------------------------------------------- uint32_t checksum() const; diff --git a/common/kernel/sdc.cc b/common/kernel/sdc.cc new file mode 100644 index 0000000000..0fbfe69009 --- /dev/null +++ b/common/kernel/sdc.cc @@ -0,0 +1,446 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 gatecat + * Copyright (C) 2024 rowanG077 + * + * + * 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 +#include + +NEXTPNR_NAMESPACE_BEGIN + +struct SdcEntity +{ + enum EntityType + { + ENTITY_CELL, + ENTITY_PORT, + ENTITY_NET, + ENTITY_PIN, + } type; + IdString name; + IdString pin; // for cell pins only + + SdcEntity(EntityType type, IdString name) : type(type), name(name) {} + SdcEntity(EntityType type, IdString name, IdString pin) : type(type), name(name), pin(pin) {} + + const std::string &to_string(Context *ctx) { return name.str(ctx); } + + CellInfo *get_cell(Context *ctx) const + { + if (type != ENTITY_CELL) + return nullptr; + return ctx->cells.at(name).get(); + } + + PortInfo *get_port(Context *ctx) const + { + if (type != ENTITY_PORT) + return nullptr; + return &ctx->ports.at(name); + } + + NetInfo *get_net(Context *ctx) const + { + if (type == ENTITY_PIN) { + CellInfo *cell = nullptr; + if (ctx->cells.count(name)) { + cell = ctx->cells.at(name).get(); + } else { + return nullptr; + } + if (!cell->ports.count(pin)) + return nullptr; + return cell->ports.at(pin).net; + } else if (type == ENTITY_NET) { + return ctx->nets.at(name).get(); + } else { + return nullptr; + } + } +}; + +struct SdcValue +{ + SdcValue(const std::string &s) : is_string(true), str(s) {}; + SdcValue(const std::vector &l) : is_string(false), list(l) {}; + + bool is_string; + std::string str; // simple string value + std::vector list; // list of entities +}; + +struct SDCParser +{ + std::string buf; + int pos = 0; + int lineno = 1; + Context *ctx; + + SDCParser(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'); + } + + inline std::string get_str() + { + std::string s; + skip_blank(false); + if (eof()) + return ""; + + bool in_quotes = false, in_braces = false, escaped = false; + + char c = get(); + + if (c == '"') + in_quotes = true; + else if (c == '{') + in_braces = true; + else + s += c; + + while (true) { + if (eof()) + log_error("EOF while parsing string '%s'\n", s.c_str()); + + char c = peek(); + if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == ']')) { + break; + } + get(); + if (escaped) { + s += c; + escaped = false; + } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { + break; + } else if (c == '\\') { + escaped = true; + } else { + s += c; + } + } + + return s; + } + + SdcValue evaluate(const std::vector &arguments) + { + NPNR_ASSERT(!arguments.empty()); + auto &arg0 = arguments.at(0); + NPNR_ASSERT(arg0.is_string); + const std::string &cmd = arg0.str; + if (cmd == "get_ports") + return cmd_get_ports(arguments); + else if (cmd == "get_cells") + return cmd_get_cells(arguments); + else if (cmd == "get_nets") + return cmd_get_nets(arguments); + else if (cmd == "get_pins") + return cmd_get_pins(arguments); + else if (cmd == "create_clock") + return cmd_create_clock(arguments); + else if (cmd == "set_false_path") + return cmd_set_false_path(arguments); + else + log_error("Unsupported SDC command '%s'\n", cmd.c_str()); + } + + std::vector get_arguments() + { + std::vector args; + while (!skip_check_eol()) { + if (check_get('[')) { + // Start of a sub-expression + auto result = evaluate(get_arguments()); + NPNR_ASSERT(check_get(']')); + args.push_back(result); + } else if (peek() == ']') { + break; + } else { + args.push_back(get_str()); + } + } + skip_blank(true); + return args; + } + + SdcValue cmd_get_nets(const std::vector &arguments) + { + std::vector nets; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_nets expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_nets (line %d)\n", s.c_str(), lineno); + IdString id = ctx->id(s); + if (ctx->nets.count(id) || ctx->net_aliases.count(id)) + nets.emplace_back(SdcEntity::ENTITY_NET, ctx->net_aliases.count(id) ? ctx->net_aliases.at(id) : id); + else + log_warning("get_nets argument '%s' matched no objects.\n", s.c_str()); + } + return nets; + } + + SdcValue cmd_get_ports(const std::vector &arguments) + { + std::vector ports; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_ports expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_ports (line %d)\n", s.c_str(), lineno); + IdString id = ctx->id(s); + if (ctx->ports.count(id)) + ports.emplace_back(SdcEntity::ENTITY_PORT, id); + } + return ports; + } + + SdcValue cmd_get_cells(const std::vector &arguments) + { + std::vector cells; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_cells expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_cells (line %d)\n", s.c_str(), lineno); + IdString id = ctx->id(s); + if (ctx->cells.count(id)) + cells.emplace_back(SdcEntity::ENTITY_CELL, id); + } + return cells; + } + + SdcValue cmd_get_pins(const std::vector &arguments) + { + std::vector pins; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_pins expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_pins (line %d)\n", s.c_str(), lineno); + auto pos = s.rfind('/'); + if (pos == std::string::npos) + log_error("expected / in cell pin name '%s' (line %d)\n", s.c_str(), lineno); + pins.emplace_back(SdcEntity::ENTITY_PIN, ctx->id(s.substr(0, pos)), ctx->id(s.substr(pos + 1))); + if (pins.back().get_net(ctx) == nullptr) { + log_warning("cell pin '%s' not found\n", s.c_str()); + pins.pop_back(); + } + } + return pins; + } + + SdcValue cmd_create_clock(const std::vector &arguments) + { + float period = 10; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (arg.is_string) { + std::string s = arg.str; + if (s == "-period") { + i++; + auto &val = arguments.at(i); + if (!val.is_string) + log_error("expecting string argument to -period (line %d)\n", lineno); + try { + period = std::stof(val.str); + } catch (std::exception &e) { + log_error("invalid argument '%s' to -period (line %d)\n", val.str.c_str(), lineno); + } + } else if (s == "-name") { + i++; + } else { + log_error("unsupported argument '%s' to create_clock\n", s.c_str()); + } + } else { + for (const auto &ety : arg.list) { + NetInfo *net = nullptr; + if (ety.type == SdcEntity::ENTITY_PIN) + net = ety.get_net(ctx); + else if (ety.type == SdcEntity::ENTITY_NET) + net = ctx->nets.at(ety.name).get(); + else if (ety.type == SdcEntity::ENTITY_PORT) + net = ctx->ports.at(ety.name).net; + else + log_error("create_clock applies only to cells, cell pins, or IO ports (line %d)\n", lineno); + + ctx->addClock(net->name, 1000.0f / period); + } + } + } + return std::string{}; + } + + SdcValue cmd_set_false_path(const std::vector &arguments) + { + NetInfo *from = nullptr; + NetInfo *to = nullptr; + + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (arg.is_string) { + std::string s = arg.str; + + bool is_from = true; + if (s == "-to") { + is_from = false; + } else if (s != "-from") { + log_error("expecting either -to or -from to set_false_path(line %d)\n", lineno); + } + + i++; + auto &val = arguments.at(i); + if (val.is_string) { + log_error("expecting SdcValue argument to -from (line %d)\n", lineno); + } + + if (val.list.size() != 1) { + log_error("Expected a single SdcEntity as argument to -to/-from (line %d)\n", lineno); + } + + auto &ety = val.list.at(0); + + NetInfo *net = nullptr; + if (ety.type == SdcEntity::ENTITY_PIN) + net = ety.get_net(ctx); + else if (ety.type == SdcEntity::ENTITY_NET) + net = ctx->nets.at(ety.name).get(); + else if (ety.type == SdcEntity::ENTITY_PORT) + net = ctx->ports.at(ety.name).net; + else + log_error("set_false_path applies only to nets, cell pins, or IO ports (line %d)\n", lineno); + + if (is_from) { + from = net; + } else { + to = net; + } + } + } + + if (from == nullptr) { + log_error("-from is required for set_false_path (line %d)\n", lineno); + } else if (to == nullptr) { + log_error("-to is required for set_false_path (line %d)\n", lineno); + } + + log_warning("set_false_path from: %s, to: %s does not do anything(yet).\n", from->name.c_str(ctx), + to->name.c_str(ctx)); + + return std::string{}; + } + + void operator()() + { + while (!eof()) { + skip_blank(true); + auto args = get_arguments(); + if (args.empty()) + continue; + evaluate(args); + } + } +}; + +void Context::read_sdc(std::istream &in) +{ + std::string buf(std::istreambuf_iterator(in), {}); + SDCParser(buf, getCtx())(); +} + +NEXTPNR_NAMESPACE_END