This commit is contained in:
Rowan Goemans 2024-09-27 23:29:47 +02:00 committed by GitHub
commit eec9a80574
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 294 additions and 65 deletions

View File

@ -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<IdString, Property> attrs;
// Path constraints set via SDC
std::vector<PathConstraint> path_constraints;
// Fmax data post timing analysis
TimingResult timing_result;

View File

@ -29,11 +29,27 @@
#include <boost/functional/hash.hpp>
#include <string>
#include <variant>
#include "hashlib.h"
#include "idstring.h"
#include "nextpnr_namespaces.h"
/* Helper struct to overload lambdas for variabt visiting
so you can do:
std::variant<int, std::string> var = 42;
std::visit(overloaded{
[](int arg) { std::cout << "Integer: " << arg << '\n'; },
[](const std::string& arg) { std::cout << "String: " << arg << '\n'; }
}, var);
*/
template <class... Ts> struct overloaded : Ts...
{
using Ts::operator()...;
};
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
NEXTPNR_NAMESPACE_BEGIN
struct GraphicElement

View File

@ -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;

View File

@ -21,12 +21,29 @@
#include "log.h"
#include "nextpnr.h"
#include "timing_constraint.h"
#include <algorithm>
#include <iterator>
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<SdcEntity> &l) : is_string(false), list(l) {};
SdcValue(const std::string &s) : is_string(true), str(s){};
SdcValue(const std::vector<SdcEntity> &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<SdcValue> &arguments)
{
std::vector<SdcEntity> 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<SdcValue> &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{};
}

View File

@ -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)

View File

@ -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<delay_t>::max(),
worst_hold_slack = std::numeric_limits<delay_t>::max();
// Forall timing constraints the uint8_t indicates
// - During forward walking
dict<exception_id_t, uint8_t> 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<PerDomain> domains;
std::vector<PerDomainPair> domain_pairs;
dict<std::pair<IdString, IdString>, delay_t> clock_delays;
// std::vector<PathConstraint> path_constraints;
std::vector<CellPortKey> topological_order;
domain_id_t async_clock_id;
exception_id_t no_exception_id;
Context *ctx;

View File

@ -0,0 +1,37 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 rowanG077 <goemansrowan@gmail.com>
*
* 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

View File

@ -0,0 +1,68 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 rowanG077 <goemansrowan@gmail.com>
*
* 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<FalsePath, MinMaxDelay, MultiCycle>;
struct PathConstraint
{
TimingException exception;
pool<CellPortKey> from;
pool<CellPortKey> to;
};
NEXTPNR_NAMESPACE_END
#endif