/* * 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