From 6deff56e836543fe6d3083d7402fd42edc00e4c7 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Tue, 28 Sep 2021 17:01:28 +0200 Subject: Moved timing result report storage to the context, added its writeout to the current utilization and fmax report Signed-off-by: Maciej Kurc --- common/command.cc | 8 -- common/nextpnr_types.h | 71 ++++++++++ common/report.cc | 123 +++++++++++++++++ common/router1.cc | 2 +- common/timing.cc | 355 ++++++++++++------------------------------------- common/timing.h | 2 +- 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(), "write render of placement to SVG file"); general.add_options()("routed-svg", po::value(), "write render of routing to SVG file"); - general.add_options()("timing-report", po::value(), - "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(); - ctx->settings[ctx->id("timing/reportFile")] = filename; - } - if (vm.count("placer")) { std::string placer = vm["placer"].as(); 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 from; + // To cell.port + std::pair 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 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 cell_port; + // Delay + delay_t delay; + // Delay budget + delay_t budget; +}; + struct TimingResult { // Achieved and target Fmax for all clock domains dict clock_fmax; + // Single domain critical paths + dict clock_paths; + // Cross-domain critical paths + std::vector xclock_paths; + + // Detailed net timing data + dict> 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> 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(""); + 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 #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 PortRefVector; typedef std::map DelayFrequency; -struct CriticalPath +struct CriticalPathData { PortRefVector ports; delay_t path_delay; delay_t path_period; }; -typedef dict CriticalPathMap; - -struct CriticalPathSegment -{ - enum class Type { - LOGIC, - ROUTING - }; - - std::pair from; - std::pair to; - Type type; - const NetInfo* net; - delay_t delay; - delay_t budget; -}; -typedef std::vector CriticalPathSegments; - -struct CriticalPathReport -{ - ClockPair clock_pair; - CriticalPathSegments segments; - delay_t period; -}; -typedef dict CriticalPathReportMap; - -struct NetSinkTiming -{ - ClockPair clock_pair; - PortRef sink; - delay_t delay; - delay_t budget; -}; +typedef dict CriticalPathDataMap; -typedef dict, hash_ptr_ops> DetailedNetTimings; +typedef dict> 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 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("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 clock_reports, - const std::vector 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(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(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 clock_reports; - std::vector xclock_reports; + bool report_critical_paths = print_path || print_fmax || update_results; - std::map clock_fmax; + dict clock_reports; + std::vector xclock_reports; + dict clock_fmax; std::set 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("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(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("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 clock_reports, - const std::vector 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(""); - 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 -- cgit v1.2.3