diff --git a/common/kernel/basectx.h b/common/kernel/basectx.h index c8791a2b96..0b8acf0ddd 100644 --- a/common/kernel/basectx.h +++ b/common/kernel/basectx.h @@ -34,6 +34,7 @@ #include "nextpnr_types.h" #include "property.h" #include "str_ring_buffer.h" +#include "timing_constraint.h" NEXTPNR_NAMESPACE_BEGIN @@ -84,6 +85,9 @@ struct BaseCtx // Context meta data dict attrs; + // Path constraints set via SDC + std::vector path_constraints; + // Fmax data post timing analysis TimingResult timing_result; diff --git a/common/kernel/nextpnr_base_types.h b/common/kernel/nextpnr_base_types.h index 80a0e24ee1..4f0d37fb1f 100644 --- a/common/kernel/nextpnr_base_types.h +++ b/common/kernel/nextpnr_base_types.h @@ -29,11 +29,27 @@ #include #include +#include #include "hashlib.h" #include "idstring.h" #include "nextpnr_namespaces.h" +/* Helper struct to overload lambdas for variabt visiting + so you can do: + std::variant var = 42; + + std::visit(overloaded{ + [](int arg) { std::cout << "Integer: " << arg << '\n'; }, + [](const std::string& arg) { std::cout << "String: " << arg << '\n'; } + }, var); +*/ +template struct overloaded : Ts... +{ + using Ts::operator()...; +}; +template overloaded(Ts...) -> overloaded; + NEXTPNR_NAMESPACE_BEGIN struct GraphicElement diff --git a/common/kernel/nextpnr_types.h b/common/kernel/nextpnr_types.h index e23cd16a35..3abdb81fb1 100644 --- a/common/kernel/nextpnr_types.h +++ b/common/kernel/nextpnr_types.h @@ -369,6 +369,27 @@ struct CellInfo : ArchCellInfo int new_offset, bool new_brackets, int width); }; +// similar to PortRef but allows storage into pool and dict +struct CellPortKey +{ + CellPortKey(){}; + CellPortKey(IdString cell, IdString port) : cell(cell), port(port){}; + explicit CellPortKey(const PortRef &pr) + { + NPNR_ASSERT(pr.cell != nullptr); + cell = pr.cell->name; + port = pr.port; + } + IdString cell, port; + unsigned int hash() const { return mkhash(cell.hash(), port.hash()); } + inline bool operator==(const CellPortKey &other) const { return (cell == other.cell) && (port == other.port); } + inline bool operator!=(const CellPortKey &other) const { return (cell != other.cell) || (port != other.port); } + inline bool operator<(const CellPortKey &other) const + { + return cell == other.cell ? port < other.port : cell < other.cell; + } +}; + struct ClockConstraint { DelayPair high; diff --git a/common/kernel/sdc.cc b/common/kernel/sdc.cc index 0fbfe69009..7e1771877a 100644 --- a/common/kernel/sdc.cc +++ b/common/kernel/sdc.cc @@ -21,12 +21,29 @@ #include "log.h" #include "nextpnr.h" +#include "timing_constraint.h" #include #include NEXTPNR_NAMESPACE_BEGIN +bool is_startpoint(Context *ctx, const PortRef &port) +{ + int clkInfoCount; + TimingPortClass cls = ctx->getPortTimingClass(port.cell, port.port, clkInfoCount); + + return cls == TMG_STARTPOINT || cls == TMG_REGISTER_OUTPUT; +} + +bool is_endpoint(Context *ctx, const PortRef &port) +{ + int clkInfoCount; + TimingPortClass cls = ctx->getPortTimingClass(port.cell, port.port, clkInfoCount); + + return cls == TMG_ENDPOINT || cls == TMG_REGISTER_OUTPUT; +} + struct SdcEntity { enum EntityType @@ -58,6 +75,23 @@ struct SdcEntity return &ctx->ports.at(name); } + PortRef get_pin(Context *ctx) const + { + if (type != ENTITY_PIN) + return PortRef{nullptr, IdString()}; + + CellInfo *cell = nullptr; + if (ctx->cells.count(name)) { + cell = ctx->cells.at(name).get(); + } else { + return PortRef{nullptr, IdString()}; + } + if (!cell->ports.count(pin)) + return PortRef{nullptr, IdString()}; + + return PortRef{cell, pin}; + } + NetInfo *get_net(Context *ctx) const { if (type == ENTITY_PIN) { @@ -80,8 +114,8 @@ struct SdcEntity struct SdcValue { - SdcValue(const std::string &s) : is_string(true), str(s) {}; - SdcValue(const std::vector &l) : is_string(false), list(l) {}; + 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 @@ -95,7 +129,7 @@ struct SDCParser int lineno = 1; Context *ctx; - SDCParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx) {}; + SDCParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; inline bool eof() const { return pos == int(buf.size()); } @@ -250,6 +284,60 @@ struct SDCParser return args; } + // Parse an argument to -from/to into the path_constraint + void sdc_into_path_constraint(const SdcEntity &ety, bool is_from, PathConstraint &ct) + { + auto &target = is_from ? ct.from : ct.to; + auto test_port = is_from ? is_startpoint : is_endpoint; + std::string tartget_str = is_from ? "startpoint" : "endpoint"; + + if (ety.type == SdcEntity::ENTITY_PIN) { + auto port_ref = ety.get_pin(ctx); + if (test_port(ctx, port_ref) == false) { + log_error("\"%s.%s\" is not a timing %s (line %d)\n", port_ref.cell->name.c_str(ctx), + port_ref.port.c_str(ctx), tartget_str.c_str(), lineno); + } + target.emplace(CellPortKey(port_ref)); + } else if (ety.type == SdcEntity::ENTITY_NET) { + auto net = ety.get_net(ctx); + if (is_from) { + auto port_ref = net->driver; + if (test_port(ctx, port_ref) == false) { + log_error("\"%s.%s\" is not a timing %s (line %d)\n", port_ref.cell->name.c_str(ctx), + port_ref.port.c_str(ctx), tartget_str.c_str(), lineno); + } + target.emplace(CellPortKey(port_ref)); + } else { + for (const auto &usr : net->users) { + if (test_port(ctx, usr) == false) { + log_error("\"%s.%s\" is not a timing %s (line %d)\n", usr.cell->name.c_str(ctx), + usr.port.c_str(ctx), tartget_str.c_str(), lineno); + } + target.emplace(CellPortKey(usr)); + } + } + } else if (ety.type == SdcEntity::ENTITY_PORT) { + auto ioport_ref = ety.get_port(ctx); + auto net = ioport_ref->net; + if (is_from) { + auto port_ref = net->driver; + if (test_port(ctx, port_ref) == false) { + log_error("\"%s.%s\" is not a timing %s (line %d)\n", port_ref.cell->name.c_str(ctx), + port_ref.port.c_str(ctx), tartget_str.c_str(), lineno); + } + target.emplace(CellPortKey(port_ref)); + } else { + for (const auto &usr : net->users) { + if (test_port(ctx, usr) == false) { + log_error("\"%s.%s\" is not a timing %s (line %d)\n", usr.cell->name.c_str(ctx), + usr.port.c_str(ctx), tartget_str.c_str(), lineno); + } + target.emplace(CellPortKey(usr)); + } + } + } + } + SdcValue cmd_get_nets(const std::vector &arguments) { std::vector nets; @@ -317,7 +405,7 @@ struct SDCParser 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) { + if (pins.back().get_pin(ctx).cell == nullptr) { log_warning("cell pin '%s' not found\n", s.c_str()); pins.pop_back(); } @@ -368,8 +456,8 @@ struct SDCParser SdcValue cmd_set_false_path(const std::vector &arguments) { - NetInfo *from = nullptr; - NetInfo *to = nullptr; + PathConstraint ct; + ct.exception = FalsePath{}; for (int i = 1; i < int(arguments.size()); i++) { auto &arg = arguments.at(i); @@ -389,38 +477,19 @@ struct SDCParser 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; + for (const auto &ety : val.list) { + sdc_into_path_constraint(ety, is_from, ct); } } } - 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); + if (ct.from.empty()) { + log_error("query specified in -from did not find any pins or ports (line %d)\n", lineno); + } else if (ct.to.empty()) { + log_error("query specified in -to did not find any pins or ports (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)); + ctx->path_constraints.emplace_back(ct); return std::string{}; } diff --git a/common/kernel/timing.cc b/common/kernel/timing.cc index 880cc50691..9d37026363 100644 --- a/common/kernel/timing.cc +++ b/common/kernel/timing.cc @@ -42,7 +42,7 @@ void TimingAnalyser::setup(bool update_net_timings, bool update_histogram, bool init_ports(); get_cell_delays(); topo_sort(); - setup_port_domains(); + setup_port_domains_and_constraints(); identify_related_domains(); run(true, update_net_timings, update_histogram, update_crit_paths); } @@ -229,7 +229,7 @@ void TimingAnalyser::topo_sort() std::swap(topological_order, topo.sorted); } -void TimingAnalyser::setup_port_domains() +void TimingAnalyser::setup_port_domains_and_constraints() { for (auto &d : domains) { d.startpoints.clear(); @@ -238,7 +238,7 @@ void TimingAnalyser::setup_port_domains() bool first_iter = true; do { // Go forward through the topological order (domains from the PoV of arrival time) - updated_domains = false; + updated_domains_constraints = false; for (auto port : topological_order) { auto &pd = ports.at(port); auto &pi = port_info(port); @@ -256,18 +256,19 @@ void TimingAnalyser::setup_port_domains() // create per-domain data pd.arrival[dom]; domains.at(dom).startpoints.emplace_back(port, fanin.other_port); + // TODO: add all constraints on this startpoint } } // copy domains across routing if (pi.net != nullptr) for (auto &usr : pi.net->users) - copy_domains(port, CellPortKey(usr), false); + propagate_domains_and_constraints(port, CellPortKey(usr), false); } else { // copy domains from input to output for (auto &fanout : pd.cell_arcs) { if (fanout.type != CellArc::COMBINATIONAL) continue; - copy_domains(port, CellPortKey(port.cell, fanout.other_port), false); + propagate_domains_and_constraints(port, CellPortKey(port.cell, fanout.other_port), false); } } } @@ -280,7 +281,7 @@ void TimingAnalyser::setup_port_domains() for (auto &fanin : pd.cell_arcs) { if (fanin.type != CellArc::COMBINATIONAL) continue; - copy_domains(port, CellPortKey(port.cell, fanin.other_port), true); + propagate_domains_and_constraints(port, CellPortKey(port.cell, fanin.other_port), true); } } else { if (first_iter) { @@ -296,11 +297,12 @@ void TimingAnalyser::setup_port_domains() // create per-domain data pd.required[dom]; domains.at(dom).endpoints.emplace_back(port, fanout.other_port); + // TODO: add all constraints on this endpoint } } // copy port to driver if (pi.net != nullptr && pi.net->driver.cell != nullptr) - copy_domains(port, CellPortKey(pi.net->driver), true); + propagate_domains_and_constraints(port, CellPortKey(pi.net->driver), true); } } // Iterate over ports and find domain pairs @@ -314,7 +316,7 @@ void TimingAnalyser::setup_port_domains() first_iter = false; // If there are loops, repeat the process until a fixed point is reached, as there might be unusual ways to // visit points, which would result in a missing domain key and therefore crash later on - } while (have_loops && updated_domains); + } while (have_loops && updated_domains_constraints); for (auto &dp : domain_pairs) { auto &launch_data = domains.at(dp.key.launch); auto &capture_data = domains.at(dp.key.capture); @@ -1294,12 +1296,25 @@ domain_id_t TimingAnalyser::domain_pair_id(domain_id_t launch, domain_id_t captu return inserted.first->second; } -void TimingAnalyser::copy_domains(const CellPortKey &from, const CellPortKey &to, bool backward) +void TimingAnalyser::propagate_domains_and_constraints(const CellPortKey &from, const CellPortKey &to, bool backward) { auto &f = ports.at(from), &t = ports.at(to); for (auto &dom : (backward ? f.required : f.arrival)) { - updated_domains |= (backward ? t.required : t.arrival).emplace(dom.first, ArrivReqTime{}).second; + updated_domains_constraints |= (backward ? t.required : t.arrival).emplace(dom.first, ArrivReqTime{}).second; } + + // for (auto &ct : f.per_constraint) { + // bool has_constraint = t.per_constraint.count(ct.first) > 0; + // bool same_constraint = has_constraint ? ct.second == t.per_constraint.at(ct.first) : false; + + // if (t.per_constraint.count(ct.first) > 0) { + // if (backward) { + // t.per_constraint[ct.first] = CONSTRAINED; + // } + // } else if (!backward) { + // t.per_constraint[ct.first] = FORWARDONLY; + // } + // } } const std::string TimingAnalyser::arcType_to_str(CellArc::ArcType typ) diff --git a/common/kernel/timing.h b/common/kernel/timing.h index f82a1b6da6..c1f4469cd3 100644 --- a/common/kernel/timing.h +++ b/common/kernel/timing.h @@ -25,26 +25,6 @@ NEXTPNR_NAMESPACE_BEGIN -struct CellPortKey -{ - CellPortKey(){}; - CellPortKey(IdString cell, IdString port) : cell(cell), port(port){}; - explicit CellPortKey(const PortRef &pr) - { - NPNR_ASSERT(pr.cell != nullptr); - cell = pr.cell->name; - port = pr.port; - } - IdString cell, port; - unsigned int hash() const { return mkhash(cell.hash(), port.hash()); } - inline bool operator==(const CellPortKey &other) const { return (cell == other.cell) && (port == other.port); } - inline bool operator!=(const CellPortKey &other) const { return (cell != other.cell) || (port != other.port); } - inline bool operator<(const CellPortKey &other) const - { - return cell == other.cell ? port < other.port : cell < other.cell; - } -}; - struct ClockDomainKey { IdString clock; @@ -58,6 +38,8 @@ struct ClockDomainKey inline bool operator==(const ClockDomainKey &other) const { return (clock == other.clock) && (edge == other.edge); } }; +typedef int exception_id_t; + typedef int domain_id_t; struct ClockDomainPairKey @@ -103,14 +85,14 @@ struct TimingAnalyser bool setup_only = false; bool have_loops = false; - bool updated_domains = false; + bool updated_domains_constraints = false; private: void init_ports(); void get_cell_delays(); void get_route_delays(); void topo_sort(); - void setup_port_domains(); + void setup_port_domains_and_constraints(); void identify_related_domains(); void reset_times(); @@ -188,6 +170,18 @@ struct TimingAnalyser : type(type), other_port(other_port), value(value), edge(edge){}; }; + // To track whether a path has a timing exception during a forwards/backwards pass. + // During the forward pass the startpoints propagate out FORWARDONLY. + // During the backwards pass all ports that contain a "FORWARDONLY" will + // move to "CONSTRAINED". Once the forward and backward passes have been + // done only the constraints on ports that are "CONSTRAINED" apply. + enum class HasPathException + { + FORWARDONLY, + BACKWARDONLY, + CONSTRAINED + }; + // Timing data for every cell port struct PerPort { @@ -205,6 +199,9 @@ struct TimingAnalyser float worst_crit = 0; delay_t worst_setup_slack = std::numeric_limits::max(), worst_hold_slack = std::numeric_limits::max(); + // Forall timing constraints the uint8_t indicates + // - During forward walking + dict per_timing_exception; }; struct PerDomain @@ -230,7 +227,7 @@ struct TimingAnalyser domain_id_t domain_id(const NetInfo *net, ClockEdge edge); domain_id_t domain_pair_id(domain_id_t launch, domain_id_t capture); - void copy_domains(const CellPortKey &from, const CellPortKey &to, bool backwards); + void propagate_domains_and_constraints(const CellPortKey &from, const CellPortKey &to, bool backwards); [[maybe_unused]] static const std::string arcType_to_str(CellArc::ArcType typ); @@ -240,10 +237,12 @@ struct TimingAnalyser std::vector domains; std::vector domain_pairs; dict, delay_t> clock_delays; + // std::vector path_constraints; std::vector topological_order; domain_id_t async_clock_id; + exception_id_t no_exception_id; Context *ctx; diff --git a/common/kernel/timing_constraint.cc b/common/kernel/timing_constraint.cc new file mode 100644 index 0000000000..20b34b09e1 --- /dev/null +++ b/common/kernel/timing_constraint.cc @@ -0,0 +1,37 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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 "timing_constraint.h" +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +const std::string MinMaxDelay::type_to_str(MinMaxDelay::Type typ) +{ + switch (typ) { + case Type::MAXDELAY: + return "MAXDELAY"; + case Type::MINDELAY: + return "MINDELAY"; + default: + log_error("Impossible MinMaxDelay::Type"); + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/kernel/timing_constraint.h b/common/kernel/timing_constraint.h new file mode 100644 index 0000000000..69a2f91007 --- /dev/null +++ b/common/kernel/timing_constraint.h @@ -0,0 +1,68 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * 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. + * + */ + +#ifndef TIMING_CONSTRAINT_H +#define TIMING_CONSTRAINT_H + +#include "nextpnr_types.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct FalsePath +{ +}; + +struct MinMaxDelay +{ + enum class Type + { + MAXDELAY, + MINDELAY + }; + + [[maybe_unused]] const std::string type_to_str(Type typ); + + Type type; + delay_t delay; + bool datapath_only; +}; + +struct MultiCycle +{ + size_t cycles; + enum class Type + { + SETUP, + HOLD + }; +}; + +using TimingException = std::variant; + +struct PathConstraint +{ + TimingException exception; + + pool from; + pool to; +}; + +NEXTPNR_NAMESPACE_END + +#endif