Merge 0e848d4fe8
into 1967db170d
This commit is contained in:
commit
eec9a80574
@ -34,6 +34,7 @@
|
|||||||
#include "nextpnr_types.h"
|
#include "nextpnr_types.h"
|
||||||
#include "property.h"
|
#include "property.h"
|
||||||
#include "str_ring_buffer.h"
|
#include "str_ring_buffer.h"
|
||||||
|
#include "timing_constraint.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -84,6 +85,9 @@ struct BaseCtx
|
|||||||
// Context meta data
|
// Context meta data
|
||||||
dict<IdString, Property> attrs;
|
dict<IdString, Property> attrs;
|
||||||
|
|
||||||
|
// Path constraints set via SDC
|
||||||
|
std::vector<PathConstraint> path_constraints;
|
||||||
|
|
||||||
// Fmax data post timing analysis
|
// Fmax data post timing analysis
|
||||||
TimingResult timing_result;
|
TimingResult timing_result;
|
||||||
|
|
||||||
|
@ -29,11 +29,27 @@
|
|||||||
|
|
||||||
#include <boost/functional/hash.hpp>
|
#include <boost/functional/hash.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "hashlib.h"
|
#include "hashlib.h"
|
||||||
#include "idstring.h"
|
#include "idstring.h"
|
||||||
#include "nextpnr_namespaces.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
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
struct GraphicElement
|
struct GraphicElement
|
||||||
|
@ -369,6 +369,27 @@ struct CellInfo : ArchCellInfo
|
|||||||
int new_offset, bool new_brackets, int width);
|
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
|
struct ClockConstraint
|
||||||
{
|
{
|
||||||
DelayPair high;
|
DelayPair high;
|
||||||
|
@ -21,12 +21,29 @@
|
|||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
|
#include "timing_constraint.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
struct SdcEntity
|
||||||
{
|
{
|
||||||
enum EntityType
|
enum EntityType
|
||||||
@ -58,6 +75,23 @@ struct SdcEntity
|
|||||||
return &ctx->ports.at(name);
|
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
|
NetInfo *get_net(Context *ctx) const
|
||||||
{
|
{
|
||||||
if (type == ENTITY_PIN) {
|
if (type == ENTITY_PIN) {
|
||||||
@ -80,8 +114,8 @@ struct SdcEntity
|
|||||||
|
|
||||||
struct SdcValue
|
struct SdcValue
|
||||||
{
|
{
|
||||||
SdcValue(const std::string &s) : is_string(true), str(s) {};
|
SdcValue(const std::string &s) : is_string(true), str(s){};
|
||||||
SdcValue(const std::vector<SdcEntity> &l) : is_string(false), list(l) {};
|
SdcValue(const std::vector<SdcEntity> &l) : is_string(false), list(l){};
|
||||||
|
|
||||||
bool is_string;
|
bool is_string;
|
||||||
std::string str; // simple string value
|
std::string str; // simple string value
|
||||||
@ -95,7 +129,7 @@ struct SDCParser
|
|||||||
int lineno = 1;
|
int lineno = 1;
|
||||||
Context *ctx;
|
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()); }
|
inline bool eof() const { return pos == int(buf.size()); }
|
||||||
|
|
||||||
@ -250,6 +284,60 @@ struct SDCParser
|
|||||||
return args;
|
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)
|
SdcValue cmd_get_nets(const std::vector<SdcValue> &arguments)
|
||||||
{
|
{
|
||||||
std::vector<SdcEntity> nets;
|
std::vector<SdcEntity> nets;
|
||||||
@ -317,7 +405,7 @@ struct SDCParser
|
|||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
log_error("expected / in cell pin name '%s' (line %d)\n", s.c_str(), lineno);
|
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)));
|
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());
|
log_warning("cell pin '%s' not found\n", s.c_str());
|
||||||
pins.pop_back();
|
pins.pop_back();
|
||||||
}
|
}
|
||||||
@ -368,8 +456,8 @@ struct SDCParser
|
|||||||
|
|
||||||
SdcValue cmd_set_false_path(const std::vector<SdcValue> &arguments)
|
SdcValue cmd_set_false_path(const std::vector<SdcValue> &arguments)
|
||||||
{
|
{
|
||||||
NetInfo *from = nullptr;
|
PathConstraint ct;
|
||||||
NetInfo *to = nullptr;
|
ct.exception = FalsePath{};
|
||||||
|
|
||||||
for (int i = 1; i < int(arguments.size()); i++) {
|
for (int i = 1; i < int(arguments.size()); i++) {
|
||||||
auto &arg = arguments.at(i);
|
auto &arg = arguments.at(i);
|
||||||
@ -389,38 +477,19 @@ struct SDCParser
|
|||||||
log_error("expecting SdcValue argument to -from (line %d)\n", lineno);
|
log_error("expecting SdcValue argument to -from (line %d)\n", lineno);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.list.size() != 1) {
|
for (const auto &ety : val.list) {
|
||||||
log_error("Expected a single SdcEntity as argument to -to/-from (line %d)\n", lineno);
|
sdc_into_path_constraint(ety, is_from, ct);
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (ct.from.empty()) {
|
||||||
log_error("-from is required for set_false_path (line %d)\n", lineno);
|
log_error("query specified in -from did not find any pins or ports (line %d)\n", lineno);
|
||||||
} else if (to == nullptr) {
|
} else if (ct.to.empty()) {
|
||||||
log_error("-to is required for set_false_path (line %d)\n", lineno);
|
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),
|
ctx->path_constraints.emplace_back(ct);
|
||||||
to->name.c_str(ctx));
|
|
||||||
|
|
||||||
return std::string{};
|
return std::string{};
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ void TimingAnalyser::setup(bool update_net_timings, bool update_histogram, bool
|
|||||||
init_ports();
|
init_ports();
|
||||||
get_cell_delays();
|
get_cell_delays();
|
||||||
topo_sort();
|
topo_sort();
|
||||||
setup_port_domains();
|
setup_port_domains_and_constraints();
|
||||||
identify_related_domains();
|
identify_related_domains();
|
||||||
run(true, update_net_timings, update_histogram, update_crit_paths);
|
run(true, update_net_timings, update_histogram, update_crit_paths);
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ void TimingAnalyser::topo_sort()
|
|||||||
std::swap(topological_order, topo.sorted);
|
std::swap(topological_order, topo.sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimingAnalyser::setup_port_domains()
|
void TimingAnalyser::setup_port_domains_and_constraints()
|
||||||
{
|
{
|
||||||
for (auto &d : domains) {
|
for (auto &d : domains) {
|
||||||
d.startpoints.clear();
|
d.startpoints.clear();
|
||||||
@ -238,7 +238,7 @@ void TimingAnalyser::setup_port_domains()
|
|||||||
bool first_iter = true;
|
bool first_iter = true;
|
||||||
do {
|
do {
|
||||||
// Go forward through the topological order (domains from the PoV of arrival time)
|
// 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) {
|
for (auto port : topological_order) {
|
||||||
auto &pd = ports.at(port);
|
auto &pd = ports.at(port);
|
||||||
auto &pi = port_info(port);
|
auto &pi = port_info(port);
|
||||||
@ -256,18 +256,19 @@ void TimingAnalyser::setup_port_domains()
|
|||||||
// create per-domain data
|
// create per-domain data
|
||||||
pd.arrival[dom];
|
pd.arrival[dom];
|
||||||
domains.at(dom).startpoints.emplace_back(port, fanin.other_port);
|
domains.at(dom).startpoints.emplace_back(port, fanin.other_port);
|
||||||
|
// TODO: add all constraints on this startpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// copy domains across routing
|
// copy domains across routing
|
||||||
if (pi.net != nullptr)
|
if (pi.net != nullptr)
|
||||||
for (auto &usr : pi.net->users)
|
for (auto &usr : pi.net->users)
|
||||||
copy_domains(port, CellPortKey(usr), false);
|
propagate_domains_and_constraints(port, CellPortKey(usr), false);
|
||||||
} else {
|
} else {
|
||||||
// copy domains from input to output
|
// copy domains from input to output
|
||||||
for (auto &fanout : pd.cell_arcs) {
|
for (auto &fanout : pd.cell_arcs) {
|
||||||
if (fanout.type != CellArc::COMBINATIONAL)
|
if (fanout.type != CellArc::COMBINATIONAL)
|
||||||
continue;
|
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) {
|
for (auto &fanin : pd.cell_arcs) {
|
||||||
if (fanin.type != CellArc::COMBINATIONAL)
|
if (fanin.type != CellArc::COMBINATIONAL)
|
||||||
continue;
|
continue;
|
||||||
copy_domains(port, CellPortKey(port.cell, fanin.other_port), true);
|
propagate_domains_and_constraints(port, CellPortKey(port.cell, fanin.other_port), true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (first_iter) {
|
if (first_iter) {
|
||||||
@ -296,11 +297,12 @@ void TimingAnalyser::setup_port_domains()
|
|||||||
// create per-domain data
|
// create per-domain data
|
||||||
pd.required[dom];
|
pd.required[dom];
|
||||||
domains.at(dom).endpoints.emplace_back(port, fanout.other_port);
|
domains.at(dom).endpoints.emplace_back(port, fanout.other_port);
|
||||||
|
// TODO: add all constraints on this endpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// copy port to driver
|
// copy port to driver
|
||||||
if (pi.net != nullptr && pi.net->driver.cell != nullptr)
|
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
|
// Iterate over ports and find domain pairs
|
||||||
@ -314,7 +316,7 @@ void TimingAnalyser::setup_port_domains()
|
|||||||
first_iter = false;
|
first_iter = false;
|
||||||
// If there are loops, repeat the process until a fixed point is reached, as there might be unusual ways to
|
// 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
|
// 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) {
|
for (auto &dp : domain_pairs) {
|
||||||
auto &launch_data = domains.at(dp.key.launch);
|
auto &launch_data = domains.at(dp.key.launch);
|
||||||
auto &capture_data = domains.at(dp.key.capture);
|
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;
|
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);
|
auto &f = ports.at(from), &t = ports.at(to);
|
||||||
for (auto &dom : (backward ? f.required : f.arrival)) {
|
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)
|
const std::string TimingAnalyser::arcType_to_str(CellArc::ArcType typ)
|
||||||
|
@ -25,26 +25,6 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
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
|
struct ClockDomainKey
|
||||||
{
|
{
|
||||||
IdString clock;
|
IdString clock;
|
||||||
@ -58,6 +38,8 @@ struct ClockDomainKey
|
|||||||
inline bool operator==(const ClockDomainKey &other) const { return (clock == other.clock) && (edge == other.edge); }
|
inline bool operator==(const ClockDomainKey &other) const { return (clock == other.clock) && (edge == other.edge); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef int exception_id_t;
|
||||||
|
|
||||||
typedef int domain_id_t;
|
typedef int domain_id_t;
|
||||||
|
|
||||||
struct ClockDomainPairKey
|
struct ClockDomainPairKey
|
||||||
@ -103,14 +85,14 @@ struct TimingAnalyser
|
|||||||
|
|
||||||
bool setup_only = false;
|
bool setup_only = false;
|
||||||
bool have_loops = false;
|
bool have_loops = false;
|
||||||
bool updated_domains = false;
|
bool updated_domains_constraints = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init_ports();
|
void init_ports();
|
||||||
void get_cell_delays();
|
void get_cell_delays();
|
||||||
void get_route_delays();
|
void get_route_delays();
|
||||||
void topo_sort();
|
void topo_sort();
|
||||||
void setup_port_domains();
|
void setup_port_domains_and_constraints();
|
||||||
void identify_related_domains();
|
void identify_related_domains();
|
||||||
|
|
||||||
void reset_times();
|
void reset_times();
|
||||||
@ -188,6 +170,18 @@ struct TimingAnalyser
|
|||||||
: type(type), other_port(other_port), value(value), edge(edge){};
|
: 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
|
// Timing data for every cell port
|
||||||
struct PerPort
|
struct PerPort
|
||||||
{
|
{
|
||||||
@ -205,6 +199,9 @@ struct TimingAnalyser
|
|||||||
float worst_crit = 0;
|
float worst_crit = 0;
|
||||||
delay_t worst_setup_slack = std::numeric_limits<delay_t>::max(),
|
delay_t worst_setup_slack = std::numeric_limits<delay_t>::max(),
|
||||||
worst_hold_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
|
struct PerDomain
|
||||||
@ -230,7 +227,7 @@ struct TimingAnalyser
|
|||||||
domain_id_t domain_id(const NetInfo *net, ClockEdge edge);
|
domain_id_t domain_id(const NetInfo *net, ClockEdge edge);
|
||||||
domain_id_t domain_pair_id(domain_id_t launch, domain_id_t capture);
|
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);
|
[[maybe_unused]] static const std::string arcType_to_str(CellArc::ArcType typ);
|
||||||
|
|
||||||
@ -240,10 +237,12 @@ struct TimingAnalyser
|
|||||||
std::vector<PerDomain> domains;
|
std::vector<PerDomain> domains;
|
||||||
std::vector<PerDomainPair> domain_pairs;
|
std::vector<PerDomainPair> domain_pairs;
|
||||||
dict<std::pair<IdString, IdString>, delay_t> clock_delays;
|
dict<std::pair<IdString, IdString>, delay_t> clock_delays;
|
||||||
|
// std::vector<PathConstraint> path_constraints;
|
||||||
|
|
||||||
std::vector<CellPortKey> topological_order;
|
std::vector<CellPortKey> topological_order;
|
||||||
|
|
||||||
domain_id_t async_clock_id;
|
domain_id_t async_clock_id;
|
||||||
|
exception_id_t no_exception_id;
|
||||||
|
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
|
|
||||||
|
37
common/kernel/timing_constraint.cc
Normal file
37
common/kernel/timing_constraint.cc
Normal 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
|
68
common/kernel/timing_constraint.h
Normal file
68
common/kernel/timing_constraint.h
Normal 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
|
Loading…
Reference in New Issue
Block a user