aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaciej Kurc <mkurc@antmicro.com>2021-09-28 17:01:28 +0200
committerMaciej Kurc <mkurc@antmicro.com>2021-09-28 17:42:51 +0200
commit6deff56e836543fe6d3083d7402fd42edc00e4c7 (patch)
treef83860a15b2d95f250f48ecfc004567c72af27d4
parentc6dc1f535acd2e9d0893ac17c9fa204e8b2370a6 (diff)
downloadnextpnr-6deff56e836543fe6d3083d7402fd42edc00e4c7.tar.gz
nextpnr-6deff56e836543fe6d3083d7402fd42edc00e4c7.tar.bz2
nextpnr-6deff56e836543fe6d3083d7402fd42edc00e4c7.zip
Moved timing result report storage to the context, added its writeout to the current utilization and fmax report
Signed-off-by: Maciej Kurc <mkurc@antmicro.com>
-rw-r--r--common/command.cc8
-rw-r--r--common/nextpnr_types.h71
-rw-r--r--common/report.cc123
-rw-r--r--common/router1.cc2
-rw-r--r--common/timing.cc355
-rw-r--r--common/timing.h2
6 files changed, 279 insertions, 282 deletions
diff --git a/common/command.cc b/common/command.cc
index 737a5b77..954a3442 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -183,9 +183,6 @@ po::options_description CommandHandler::getGeneralOptions()
general.add_options()("placed-svg", po::value<std::string>(), "write render of placement to SVG file");
general.add_options()("routed-svg", po::value<std::string>(), "write render of routing to SVG file");
- general.add_options()("timing-report", po::value<std::string>(),
- "write timing analysis report in CSV format to file");
-
return general;
}
@@ -252,11 +249,6 @@ void CommandHandler::setupContext(Context *ctx)
ctx->settings[ctx->id("timing/allowFail")] = true;
}
- if (vm.count("timing-report")) {
- std::string filename = vm["timing-report"].as<std::string>();
- ctx->settings[ctx->id("timing/reportFile")] = filename;
- }
-
if (vm.count("placer")) {
std::string placer = vm["placer"].as<std::string>();
if (std::find(Arch::availablePlacers.begin(), Arch::availablePlacers.end(), placer) ==
diff --git a/common/nextpnr_types.h b/common/nextpnr_types.h
index 1cae3dbe..17261095 100644
--- a/common/nextpnr_types.h
+++ b/common/nextpnr_types.h
@@ -223,10 +223,81 @@ struct ClockFmax
float constraint;
};
+struct ClockEvent
+{
+ IdString clock;
+ ClockEdge edge;
+
+ bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
+ unsigned int hash() const { return mkhash(clock.hash(), int(edge)); }
+};
+
+struct ClockPair
+{
+ ClockEvent start, end;
+
+ bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
+ unsigned int hash() const { return mkhash(start.hash(), end.hash()); }
+};
+
+struct CriticalPath
+{
+ struct Segment {
+
+ // Segment type
+ enum class Type {
+ LOGIC,
+ ROUTING
+ };
+
+ // Type
+ Type type;
+ // Net name (routing only)
+ IdString net;
+ // From cell.port
+ std::pair<IdString, IdString> from;
+ // To cell.port
+ std::pair<IdString, IdString> to;
+ // Segment delay
+ delay_t delay;
+ // Segment budget (routing only)
+ delay_t budget;
+ };
+
+ // Clock pair
+ ClockPair clock_pair;
+ // Total path delay
+ delay_t delay;
+ // Period (max allowed delay)
+ delay_t period;
+ // Individual path segments
+ std::vector<Segment> segments;
+};
+
+// Holds timing information of a single source to sink path of a net
+struct NetSinkTiming
+{
+ // Clock event pair
+ ClockPair clock_pair;
+ // Cell and port (the sink)
+ std::pair<IdString, IdString> cell_port;
+ // Delay
+ delay_t delay;
+ // Delay budget
+ delay_t budget;
+};
+
struct TimingResult
{
// Achieved and target Fmax for all clock domains
dict<IdString, ClockFmax> clock_fmax;
+ // Single domain critical paths
+ dict<IdString, CriticalPath> clock_paths;
+ // Cross-domain critical paths
+ std::vector<CriticalPath> xclock_paths;
+
+ // Detailed net timing data
+ dict<IdString, std::vector<NetSinkTiming>> detailed_net_timings;
};
// Represents the contents of a non-leaf cell in a design
diff --git a/common/report.cc b/common/report.cc
index 4b3b2418..a595777a 100644
--- a/common/report.cc
+++ b/common/report.cc
@@ -40,6 +40,127 @@ dict<IdString, std::pair<int, int>> get_utilization(const Context *ctx)
return result;
}
} // namespace
+
+static std::string clock_event_name (const Context* ctx, const ClockEvent& e) {
+ std::string value;
+ if (e.clock == ctx->id("$async$"))
+ value = std::string("<async>");
+ else
+ value = (e.edge == FALLING_EDGE ? std::string("negedge ") :
+ std::string("posedge ")) + e.clock.str(ctx);
+ return value;
+};
+
+static Json::array report_critical_paths (const Context* ctx) {
+
+ auto report_critical_path = [ctx](const CriticalPath& report) {
+ Json::array pathJson;
+
+ for (const auto& segment : report.segments) {
+
+ const auto& driver = ctx->cells.at(segment.from.first);
+ const auto& sink = ctx->cells.at(segment.to.first);
+
+ auto fromLoc = ctx->getBelLocation(driver->bel);
+ auto toLoc = ctx->getBelLocation(sink->bel);
+
+ auto fromJson = Json::object({
+ {"cell", segment.from.first.c_str(ctx)},
+ {"port", segment.from.second.c_str(ctx)},
+ {"loc", Json::array({fromLoc.x, fromLoc.y})}
+ });
+
+ auto toJson = Json::object({
+ {"cell", segment.to.first.c_str(ctx)},
+ {"port", segment.to.second.c_str(ctx)},
+ {"loc", Json::array({toLoc.x, toLoc.y})}
+ });
+
+ auto segmentJson = Json::object({
+ {"delay", ctx->getDelayNS(segment.delay)},
+ {"from", fromJson},
+ {"to", toJson},
+ });
+
+ if (segment.type == CriticalPath::Segment::Type::LOGIC) {
+ segmentJson["type"] = "logic";
+ }
+ else if (segment.type == CriticalPath::Segment::Type::ROUTING) {
+ segmentJson["type"] = "routing";
+ segmentJson["net"] = segment.net.c_str(ctx);
+ segmentJson["budget"] = ctx->getDelayNS(segment.budget);
+ }
+
+ pathJson.push_back(segmentJson);
+ }
+
+ return pathJson;
+ };
+
+ auto critPathsJson = Json::array();
+
+ // Critical paths
+ for (auto &report : ctx->timing_result.clock_paths) {
+
+ critPathsJson.push_back(Json::object({
+ {"from", clock_event_name(ctx, report.second.clock_pair.start)},
+ {"to", clock_event_name(ctx, report.second.clock_pair.end)},
+ {"path", report_critical_path(report.second)}
+ }));
+ }
+
+ // Cross-domain paths
+ for (auto &report : ctx->timing_result.xclock_paths) {
+ critPathsJson.push_back(Json::object({
+ {"from", clock_event_name(ctx, report.clock_pair.start)},
+ {"to", clock_event_name(ctx, report.clock_pair.end)},
+ {"path", report_critical_path(report)}
+ }));
+ }
+
+ return critPathsJson;
+}
+
+static Json::array report_detailed_net_timings (const Context* ctx) {
+ auto detailedNetTimingsJson = Json::array();
+
+ // Detailed per-net timing analysis
+ for (const auto& it : ctx->timing_result.detailed_net_timings) {
+
+ const NetInfo* net = ctx->nets.at(it.first).get();
+ ClockEvent start = it.second[0].clock_pair.start;
+
+ Json::array endpointsJson;
+ for (const auto& sink_timing : it.second) {
+
+ // FIXME: Is it possible that there are multiple different start
+ // events for a single net? It has a single driver
+ NPNR_ASSERT(sink_timing.clock_pair.start == start);
+
+ auto endpointJson = Json::object({
+ {"cell", sink_timing.cell_port.first.c_str(ctx)},
+ {"port", sink_timing.cell_port.second.c_str(ctx)},
+ {"event", clock_event_name(ctx, sink_timing.clock_pair.end)},
+ {"delay", ctx->getDelayNS(sink_timing.delay)},
+ {"budget", ctx->getDelayNS(sink_timing.budget)}
+ });
+ endpointsJson.push_back(endpointJson);
+ }
+
+ auto netTimingJson = Json::object({
+ {"net", net->name.c_str(ctx)},
+ {"driver", net->driver.cell->name.c_str(ctx)},
+ {"port", net->driver.port.c_str(ctx)},
+ {"event", clock_event_name(ctx, start)},
+ {"endpoints", endpointsJson}
+ });
+
+ detailedNetTimingsJson.push_back(netTimingJson);
+ }
+
+ return detailedNetTimingsJson;
+}
+
void Context::writeReport(std::ostream &out) const
{
auto util = get_utilization(this);
@@ -60,6 +181,8 @@ void Context::writeReport(std::ostream &out) const
out << Json(Json::object{
{"utilization", util_json},
{"fmax", fmax_json},
+ {"critical_paths", report_critical_paths(this)},
+ {"detailed_net_timings", report_detailed_net_timings(this)}
})
.dump()
<< std::endl;
diff --git a/common/router1.cc b/common/router1.cc
index d62e776a..c8585b0b 100644
--- a/common/router1.cc
+++ b/common/router1.cc
@@ -874,7 +874,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
log_info("Checksum: 0x%08x\n", ctx->checksum());
timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */,
- true /* warn_on_failure */, true /* write_report */);
+ true /* warn_on_failure */, true /* update_results */);
return true;
} catch (log_execution_error_exception) {
diff --git a/common/timing.cc b/common/timing.cc
index 8c4002d0..369b6579 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -26,12 +26,9 @@
#include <utility>
#include "log.h"
#include "util.h"
-#include "json11.hpp"
NEXTPNR_NAMESPACE_BEGIN
-using namespace json11;
-
void TimingAnalyser::setup()
{
init_ports();
@@ -609,70 +606,19 @@ PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.
/** LEGACY CODE BEGIN **/
-namespace {
-struct ClockEvent
-{
- IdString clock;
- ClockEdge edge;
-
- bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
- unsigned int hash() const { return mkhash(clock.hash(), int(edge)); }
-};
-
-struct ClockPair
-{
- ClockEvent start, end;
-
- bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
- unsigned int hash() const { return mkhash(start.hash(), end.hash()); }
-};
-} // namespace
-
typedef std::vector<const PortRef *> PortRefVector;
typedef std::map<int, unsigned> DelayFrequency;
-struct CriticalPath
+struct CriticalPathData
{
PortRefVector ports;
delay_t path_delay;
delay_t path_period;
};
-typedef dict<ClockPair, CriticalPath> CriticalPathMap;
-
-struct CriticalPathSegment
-{
- enum class Type {
- LOGIC,
- ROUTING
- };
-
- std::pair<CellInfo*,IdString> from;
- std::pair<CellInfo*,IdString> to;
- Type type;
- const NetInfo* net;
- delay_t delay;
- delay_t budget;
-};
-typedef std::vector<CriticalPathSegment> CriticalPathSegments;
-
-struct CriticalPathReport
-{
- ClockPair clock_pair;
- CriticalPathSegments segments;
- delay_t period;
-};
-typedef dict<ClockPair, CriticalPathReport> CriticalPathReportMap;
-
-struct NetSinkTiming
-{
- ClockPair clock_pair;
- PortRef sink;
- delay_t delay;
- delay_t budget;
-};
+typedef dict<ClockPair, CriticalPathData> CriticalPathDataMap;
-typedef dict<const NetInfo*, std::vector<NetSinkTiming>, hash_ptr_ops> DetailedNetTimings;
+typedef dict<IdString, std::vector<NetSinkTiming>> DetailedNetTimings;
struct Timing
{
@@ -680,7 +626,7 @@ struct Timing
bool net_delays;
bool update;
delay_t min_slack;
- CriticalPathMap *crit_path;
+ CriticalPathDataMap *crit_path;
DelayFrequency *slack_histogram;
DetailedNetTimings *detailed_net_timings;
IdString async_clock;
@@ -697,7 +643,7 @@ struct Timing
dict<ClockEvent, delay_t> arrival_time;
};
- Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
+ Timing(Context *ctx, bool net_delays, bool update, CriticalPathDataMap *crit_path = nullptr,
DelayFrequency *slack_histogram = nullptr,
DetailedNetTimings *detailed_net_timings = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->setting<float>("target_freq")),
@@ -992,11 +938,11 @@ struct Timing
if (detailed_net_timings) {
NetSinkTiming sink_timing;
sink_timing.clock_pair = clockPair;
- sink_timing.sink = usr;
+ sink_timing.cell_port = std::make_pair(usr.cell->name, usr.port);
sink_timing.delay = endpoint_arrival;
sink_timing.budget = period;
- (*detailed_net_timings)[net].push_back(sink_timing);
+ (*detailed_net_timings)[net->name].push_back(sink_timing);
}
if (crit_path) {
@@ -1162,17 +1108,9 @@ void assign_budget(Context *ctx, bool quiet)
log_info("Checksum: 0x%08x\n", ctx->checksum());
}
-void write_timing_report(
- Context* ctx,
- const std::string& file_name,
- const std::map<IdString, CriticalPathReport> clock_reports,
- const std::vector<CriticalPathReport> xclock_reports,
- const DetailedNetTimings& detailed_net_timings
-);
+CriticalPath build_critical_path_report(Context* ctx, ClockPair &clocks, const PortRefVector &crit_path) {
-CriticalPathReport build_critical_path_report(Context* ctx, ClockPair &clocks, const PortRefVector &crit_path) {
-
- CriticalPathReport report;
+ CriticalPath report;
report.clock_pair = clocks;
auto &front = crit_path.front();
@@ -1217,24 +1155,24 @@ CriticalPathReport build_critical_path_report(Context* ctx, ClockPair &clocks, c
ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay);
}
- CriticalPathSegment seg_logic;
- seg_logic.type = CriticalPathSegment::Type::LOGIC;
+ CriticalPath::Segment seg_logic;
+ seg_logic.type = CriticalPath::Segment::Type::LOGIC;
seg_logic.delay = comb_delay.maxDelay();
seg_logic.budget = 0;
- seg_logic.from = std::make_pair(const_cast<CellInfo*>(last_cell), last_port);
- seg_logic.to = std::make_pair(driver_cell, driver.port);
- seg_logic.net = nullptr;
+ seg_logic.from = std::make_pair(last_cell->name, last_port);
+ seg_logic.to = std::make_pair(driver_cell->name, driver.port);
+ seg_logic.net = IdString();
report.segments.push_back(seg_logic);
auto net_delay = ctx->getNetinfoRouteDelay(net, *sink);
- CriticalPathSegment seg_route;
- seg_route.type = CriticalPathSegment::Type::ROUTING;
+ CriticalPath::Segment seg_route;
+ seg_route.type = CriticalPath::Segment::Type::ROUTING;
seg_route.delay = net_delay;
seg_route.budget = sink->budget;
- seg_route.from = std::make_pair(driver_cell, driver.port);
- seg_route.to = std::make_pair(sink_cell, sink->port);
- seg_route.net = net;
+ seg_route.from = std::make_pair(driver_cell->name, driver.port);
+ seg_route.to = std::make_pair(sink_cell->name, sink->port);
+ seg_route.net = net->name;
report.segments.push_back(seg_route);
last_cell = sink_cell;
@@ -1247,20 +1185,20 @@ CriticalPathReport build_critical_path_report(Context* ctx, ClockPair &clocks, c
auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back()->cell, crit_path.back()->port, 0);
delay_t setup = sinkClockInfo.setup.maxDelay();
- CriticalPathSegment seg_logic;
- seg_logic.type = CriticalPathSegment::Type::LOGIC;
+ CriticalPath::Segment seg_logic;
+ seg_logic.type = CriticalPath::Segment::Type::LOGIC;
seg_logic.delay = setup;
seg_logic.budget = 0;
- seg_logic.from = std::make_pair(const_cast<CellInfo*>(last_cell), last_port);
+ seg_logic.from = std::make_pair(last_cell->name, last_port);
seg_logic.to = seg_logic.from;
- seg_logic.net = nullptr;
+ seg_logic.net = IdString();
report.segments.push_back(seg_logic);
}
return report;
}
-void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure, bool write_report)
+void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path, bool warn_on_failure, bool update_results)
{
auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
std::string value;
@@ -1273,20 +1211,19 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
return value;
};
- CriticalPathMap crit_paths;
+ CriticalPathDataMap crit_paths;
DelayFrequency slack_histogram;
DetailedNetTimings detailed_net_timings;
Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr,
- print_histogram ? &slack_histogram : nullptr, write_report ? &detailed_net_timings : nullptr);
+ print_histogram ? &slack_histogram : nullptr, update_results ? &detailed_net_timings : nullptr);
timing.walk_paths();
- bool report_critical_paths = print_path || print_fmax || write_report;
-
- std::map<IdString, CriticalPathReport> clock_reports;
- std::vector<CriticalPathReport> xclock_reports;
+ bool report_critical_paths = print_path || print_fmax || update_results;
- std::map<IdString, double> clock_fmax;
+ dict<IdString, CriticalPath> clock_reports;
+ std::vector<CriticalPath> xclock_reports;
+ dict<IdString, ClockFmax> clock_fmax;
std::set<IdString> empty_clocks; // set of clocks with no interior paths
if (report_critical_paths) {
@@ -1308,8 +1245,9 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
Fmax = 1000 / ctx->getDelayNS(path.second.path_delay);
else
Fmax = 500 / ctx->getDelayNS(path.second.path_delay);
- if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) {
- clock_fmax[a.clock] = Fmax;
+ if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock).achieved) {
+ clock_fmax[a.clock].achieved = Fmax;
+ clock_fmax[a.clock].constraint = 0.0f; // Will be filled later
clock_reports[a.clock] = build_critical_path_report(ctx, path.first, path.second.ports);
clock_reports[a.clock].period = path.second.path_period;
}
@@ -1330,7 +1268,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
log_info("No Fmax available; no interior timing paths found in design.\n");
}
- std::sort(xclock_reports.begin(), xclock_reports.end(), [ctx](const CriticalPathReport &ra, const CriticalPathReport &rb) {
+ std::sort(xclock_reports.begin(), xclock_reports.end(), [ctx](const CriticalPath &ra, const CriticalPath &rb) {
const auto& a = ra.clock_pair;
const auto& b = rb.clock_pair;
@@ -1351,26 +1289,45 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
return true;
return false;
});
+
+ for (auto &clock : clock_reports) {
+ float target = ctx->setting<float>("target_freq") / 1e6;
+ if (ctx->nets.at(clock.first)->clkconstr)
+ target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
+ clock_fmax[clock.first].constraint = target;
+ }
+ }
+
+ // Update timing results in the context
+ if (update_results) {
+ auto& results = ctx->timing_result;
+
+ results.clock_fmax = std::move(clock_fmax);
+ results.clock_paths = std::move(clock_reports);
+ results.xclock_paths = std::move(xclock_reports);
+
+ results.detailed_net_timings = std::move(detailed_net_timings);
}
// Print critical paths
if (print_path) {
- auto print_path_report = [ctx](const CriticalPathReport& report) {
+ // A helper function for reporting one critical path
+ auto print_path_report = [ctx](const CriticalPath& path) {
delay_t total = 0, logic_total = 0, route_total = 0;
log_info("curr total\n");
- for (const auto& segment : report.segments) {
+ for (const auto& segment : path.segments) {
total += segment.delay;
- if (segment.type == CriticalPathSegment::Type::LOGIC) {
+ if (segment.type == CriticalPath::Segment::Type::LOGIC) {
logic_total += segment.delay;
if (segment.from != segment.to) {
log_info("%4.1f %4.1f Source %s.%s\n",
ctx->getDelayNS(segment.delay),
ctx->getDelayNS(total),
- segment.from.first->name.c_str(ctx),
+ segment.from.first.c_str(ctx),
segment.from.second.c_str(ctx)
);
}
@@ -1378,43 +1335,46 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
log_info("%4.1f %4.1f Setup %s.%s\n",
ctx->getDelayNS(segment.delay),
ctx->getDelayNS(total),
- segment.to.first->name.c_str(ctx),
+ segment.to.first.c_str(ctx),
segment.to.second.c_str(ctx)
);
}
}
- if (segment.type == CriticalPathSegment::Type::ROUTING) {
+ if (segment.type == CriticalPath::Segment::Type::ROUTING) {
route_total += segment.delay;
- auto driver_loc = ctx->getBelLocation(segment.from.first->bel);
- auto sink_loc = ctx->getBelLocation(segment.to.first->bel);
+ const auto& driver = ctx->cells.at(segment.from.first);
+ const auto& sink = ctx->cells.at(segment.to.first);
+
+ auto driver_loc = ctx->getBelLocation(driver->bel);
+ auto sink_loc = ctx->getBelLocation(sink->bel);
log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n",
ctx->getDelayNS(segment.delay),
ctx->getDelayNS(total),
- segment.net->name.c_str(ctx),
+ segment.net.c_str(ctx),
ctx->getDelayNS(segment.budget),
driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y
);
log_info(" Sink %s.%s\n",
- segment.to.first->name.c_str(ctx),
+ segment.to.first.c_str(ctx),
segment.to.second.c_str(ctx)
);
if (ctx->verbose) {
- PortRef sink;
- sink.cell = segment.to.first;
- sink.port = segment.to.second;
- sink.budget = segment.budget;
+ PortRef sink_ref;
+ sink_ref.cell = sink.get();
+ sink_ref.port = segment.to.second;
+ sink_ref.budget = segment.budget;
- auto net = segment.net;
+ const NetInfo* net = ctx->nets.at(segment.net).get();
auto driver_wire = ctx->getNetinfoSourceWire(net);
- auto sink_wire = ctx->getNetinfoSinkWire(net, sink, 0);
+ auto sink_wire = ctx->getNetinfoSinkWire(net, sink_ref, 0);
log_info(" prediction: %f ns estimate: %f ns\n",
- ctx->getDelayNS(ctx->predictDelay(net, sink)),
+ ctx->getDelayNS(ctx->predictDelay(net, sink_ref)),
ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire)));
auto cursor = sink_wire;
delay_t delay;
@@ -1437,6 +1397,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
log_info("%.1f ns logic, %.1f ns routing\n", ctx->getDelayNS(logic_total), ctx->getDelayNS(route_total));
};
+ // Single domain paths
for (auto &clock : clock_reports) {
log_break();
std::string start =
@@ -1449,6 +1410,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
print_path_report(report);
}
+ // Cross-domain paths
for (auto &report : xclock_reports) {
log_break();
std::string start = format_event(report.clock_pair.start);
@@ -1460,31 +1422,28 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
if (print_fmax) {
log_break();
+
unsigned max_width = 0;
- auto &result = ctx->timing_result;
- result.clock_fmax.clear();
for (auto &clock : clock_reports)
max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
+
for (auto &clock : clock_reports) {
const auto &clock_name = clock.first.str(ctx);
const int width = max_width - clock_name.size();
- float target = ctx->setting<float>("target_freq") / 1e6;
- if (ctx->nets.at(clock.first)->clkconstr)
- target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
- result.clock_fmax[clock.first].achieved = clock_fmax[clock.first];
- result.clock_fmax[clock.first].constraint = target;
+ float fmax = clock_fmax[clock.first].achieved;
+ float target = clock_fmax[clock.first].constraint;
+ bool passed = target < fmax;
- bool passed = target < clock_fmax[clock.first];
if (!warn_on_failure || passed)
log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
- clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
+ clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target);
else if (bool_or_default(ctx->settings, ctx->id("timing/allowFail"), false))
log_warning("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
- clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
+ clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target);
else
log_nonfatal_error("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
- clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
+ clock_name.c_str(), fmax, passed ? "PASS" : "FAIL", target);
}
for (auto &eclock : empty_clocks) {
if (eclock != ctx->id("$async$"))
@@ -1540,154 +1499,6 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
std::string(bins[i] * bar_width / max_freq, '*').c_str(),
(bins[i] * bar_width) % max_freq > 0 ? '+' : ' ');
}
-
- // Write detailed timing analysis report to file
- std::string report_file;
- if (write_report) {
- if (ctx->settings.count(ctx->id("timing/reportFile"))) {
- report_file = ctx->settings[ctx->id("timing/reportFile")].as_string();
- }
- }
-
- if (!report_file.empty()) {
- log_break();
- log_info("Writing timing analysis report...\n");
- write_timing_report(ctx, report_file,
- clock_reports,
- xclock_reports,
- detailed_net_timings);
- }
-}
-
-void write_timing_report(
- Context* ctx,
- const std::string& file_name,
- const std::map<IdString, CriticalPathReport> clock_reports,
- const std::vector<CriticalPathReport> xclock_reports,
- const DetailedNetTimings& detailed_net_timings
-)
-{
- auto event_name = [ctx](const ClockEvent &e) {
- std::string value;
- if (e.clock == ctx->id("$async$"))
- value = std::string("<async>");
- else
- value = (e.edge == FALLING_EDGE ? std::string("negedge ") :
- std::string("posedge ")) + e.clock.str(ctx);
- return value;
- };
-
- auto report_critical_path = [ctx](const CriticalPathReport& report) {
- Json::array pathJson;
-
- for (const auto& segment : report.segments) {
-
- auto fromLoc = ctx->getBelLocation(segment.from.first->bel);
- auto toLoc = ctx->getBelLocation(segment.to.first->bel);
-
- auto fromJson = Json::object({
- {"cell", segment.from.first->name.c_str(ctx)},
- {"port", segment.from.second.c_str(ctx)},
- {"loc", Json::array({fromLoc.x, fromLoc.y})}
- });
-
- auto toJson = Json::object({
- {"cell", segment.to.first->name.c_str(ctx)},
- {"port", segment.to.second.c_str(ctx)},
- {"loc", Json::array({toLoc.x, toLoc.y})}
- });
-
- auto segmentJson = Json::object({
- {"delay", ctx->getDelayNS(segment.delay)},
- {"from", fromJson},
- {"to", toJson},
- });
-
- if (segment.type == CriticalPathSegment::Type::LOGIC) {
- segmentJson["type"] = "logic";
- }
- else if (segment.type == CriticalPathSegment::Type::ROUTING) {
- segmentJson["type"] = "routing";
- segmentJson["net"] = segment.net->name.c_str(ctx);
- segmentJson["budget"] = ctx->getDelayNS(segment.budget);
- }
-
- pathJson.push_back(segmentJson);
- }
-
- return pathJson;
- };
-
- // Open the file
- FILE* fp = fopen(file_name.c_str(), "w");
- NPNR_ASSERT(fp != nullptr);
-
- // Critical paths
- auto critPathsJson = Json::array();
- for (auto &report : clock_reports) {
-
- critPathsJson.push_back(Json::object({
- {"from", event_name(report.second.clock_pair.start)},
- {"to", event_name(report.second.clock_pair.end)},
- {"path", report_critical_path(report.second)}
- }));
- }
-
- // Cross-domain paths
- for (auto &report : xclock_reports) {
- critPathsJson.push_back(Json::object({
- {"from", event_name(report.clock_pair.start)},
- {"to", event_name(report.clock_pair.end)},
- {"path", report_critical_path(report)}
- }));
- }
-
- // Detailed per-net timing analysis
- auto detailedNetTimingsJson = Json::array();
- for (const auto& it : detailed_net_timings) {
- const NetInfo* net = it.first;
-
- ClockEvent start = it.second[0].clock_pair.start;
-
- Json::array endpointsJson;
- for (const auto& sink_timing : it.second) {
-
- // FIXME: Is it possible that there are multiple different start
- // events for a single net? It has a single driver
- NPNR_ASSERT(sink_timing.clock_pair.start == start);
-
- auto endpointJson = Json::object({
- {"cell", sink_timing.sink.cell->name.c_str(ctx)},
- {"port", sink_timing.sink.port.c_str(ctx)},
- {"event", event_name(sink_timing.clock_pair.end)},
- {"delay", ctx->getDelayNS(sink_timing.delay)},
- {"budget", ctx->getDelayNS(sink_timing.budget)}
- });
- endpointsJson.push_back(endpointJson);
- }
-
- auto netTimingJson = Json::object({
- {"net", net->name.c_str(ctx)},
- {"driver", net->driver.cell->name.c_str(ctx)},
- {"port", net->driver.port.c_str(ctx)},
- {"event", event_name(start)},
- {"endpoints", endpointsJson}
- });
- detailedNetTimingsJson.push_back(netTimingJson);
- }
-
- auto analysisJson = Json::object({
- {"critical_paths", Json(critPathsJson)},
- {"detailed_net_timings", Json(detailedNetTimingsJson)}
- });
-
- // Assemble and serialize the final Json
- auto jsonRoot = Json(Json::object({
- {"timing_analysis", Json(analysisJson)}
- }));
-
- fputs(jsonRoot.dump().c_str(), fp);
- fclose(fp);
}
NEXTPNR_NAMESPACE_END
diff --git a/common/timing.h b/common/timing.h
index 4645077a..b34fd636 100644
--- a/common/timing.h
+++ b/common/timing.h
@@ -252,7 +252,7 @@ void assign_budget(Context *ctx, bool quiet = false);
// Perform timing analysis and print out the fmax, and optionally the
// critical path
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
- bool warn_on_failure = false, bool write_report = false);
+ bool warn_on_failure = false, bool update_results = false);
NEXTPNR_NAMESPACE_END