Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timing exception support #1372

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions common/kernel/basectx.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "nextpnr_types.h"
#include "property.h"
#include "str_ring_buffer.h"
#include "timing_constraint.h"

NEXTPNR_NAMESPACE_BEGIN

Expand Down Expand Up @@ -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;

Expand Down
16 changes: 16 additions & 0 deletions common/kernel/nextpnr_base_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions common/kernel/nextpnr_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
133 changes: 101 additions & 32 deletions common/kernel/sdc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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()); }

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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{};
}
Expand Down
35 changes: 25 additions & 10 deletions common/kernel/timing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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);
}
}
}
Expand All @@ -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) {
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
Loading