From 094f0cc3425755f4e835242af47afc5c28efe92d Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Sat, 14 Sep 2024 00:15:24 +0200 Subject: [PATCH] timing: integrate c2c delays and cleanup code --- common/kernel/context.cc | 10 +- common/kernel/nextpnr_types.h | 12 +- common/kernel/timing.cc | 220 +++++++++++++++++++++------------- common/kernel/timing.h | 18 +-- common/kernel/timing_log.cc | 22 ++-- 5 files changed, 169 insertions(+), 113 deletions(-) diff --git a/common/kernel/context.cc b/common/kernel/context.cc index 0e99c1b6..b194675d 100644 --- a/common/kernel/context.cc +++ b/common/kernel/context.cc @@ -116,10 +116,10 @@ delay_t Context::predictArcDelay(const NetInfo *net_info, const PortRef &sink) c delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const { - // #ifdef ARCH_ECP5 - // if (net_info->is_global) - // return 0; - // #endif +#ifdef ARCH_ECP5 + if (net_info->is_global) + return 0; +#endif if (net_info->wires.empty()) return predictArcDelay(net_info, user_info); @@ -417,7 +417,7 @@ void Context::check() const namespace { struct FixupHierarchyWorker { - FixupHierarchyWorker(Context *ctx) : ctx(ctx) {}; + FixupHierarchyWorker(Context *ctx) : ctx(ctx){}; Context *ctx; void run() { diff --git a/common/kernel/nextpnr_types.h b/common/kernel/nextpnr_types.h index 23b319b9..292508b2 100644 --- a/common/kernel/nextpnr_types.h +++ b/common/kernel/nextpnr_types.h @@ -81,7 +81,7 @@ struct PortRef // minimum and maximum delay struct DelayPair { - DelayPair() : min_delay(0), max_delay(0) {}; + DelayPair() : min_delay(0), max_delay(0){}; explicit DelayPair(delay_t delay) : min_delay(delay), max_delay(delay) {} DelayPair(delay_t min_delay, delay_t max_delay) : min_delay(min_delay), max_delay(max_delay) {} delay_t minDelay() const { return min_delay; } @@ -278,7 +278,7 @@ struct PseudoCell virtual bool getDelay(IdString fromPort, IdString toPort, DelayQuad &delay) const = 0; virtual TimingPortClass getPortTimingClass(IdString port, int &clockInfoCount) const = 0; virtual TimingClockingInfo getPortClockingInfo(IdString port, int index) const = 0; - virtual ~PseudoCell() {}; + virtual ~PseudoCell(){}; }; struct RegionPlug : PseudoCell @@ -398,16 +398,20 @@ struct CriticalPath // Segment type enum class Type { + CLK_SKEW, // Clock skew CLK_TO_Q, // Clock-to-Q delay SOURCE, // Delayless source LOGIC, // Combinational logic delay ROUTING, // Routing delay - SETUP // Setup time in sink + SETUP, // Setup time in sink + HOLD // Hold time in sink }; [[maybe_unused]] static const std::string type_to_str(Type typ) { switch (typ) { + case Type::CLK_SKEW: + return "CLK_SKEW"; case Type::CLK_TO_Q: return "CLK_TO_Q"; case Type::SOURCE: @@ -418,6 +422,8 @@ struct CriticalPath return "ROUTING"; case Type::SETUP: return "SETUP"; + case Type::HOLD: + return "HOLD"; default: log_error("Impossible Segment::Type"); } diff --git a/common/kernel/timing.cc b/common/kernel/timing.cc index 20b95d3c..e9a7b022 100644 --- a/common/kernel/timing.cc +++ b/common/kernel/timing.cc @@ -175,19 +175,10 @@ void TimingAnalyser::get_route_delays() if (ni->driver.cell == nullptr || ni->driver.cell->bel == BelId()) continue; - if (clock_skew) { - printf("net %s has driver %s.%s\n", ni->name.c_str(ctx), ni->driver.cell->name.c_str(ctx), - ni->driver.port.c_str(ctx)); - } for (auto &usr : ni->users) { if (usr.cell->bel == BelId()) continue; ports.at(CellPortKey(usr)).route_delay = DelayPair(ctx->getNetinfoRouteDelay(ni, usr)); - - if (clock_skew) { - printf("\tuser %s.%s, delay: %f\n", usr.cell->name.c_str(ctx), usr.port.c_str(ctx), - ctx->getDelayNS(ctx->getNetinfoRouteDelay(ni, usr))); - } } } } @@ -582,9 +573,10 @@ void TimingAnalyser::walk_forward() for (auto &fanin : pd.cell_arcs) { if (fanin.type == CellArc::CLK_TO_Q && fanin.other_port == sp.second) { init_arrival += fanin.value.delayPair(); - if (clock_skew) { + // Include the clock delay if clock_skew analysis is enabled + if (with_clock_skew) { auto clock_delay = ports.at(CellPortKey(sp.first.cell, fanin.other_port)).route_delay; - init_arrival += clock_delay; + init_arrival += DelayPair(clock_delay.min_delay); } break; } @@ -638,13 +630,11 @@ void TimingAnalyser::walk_backward() if (ep.second != IdString()) { // Add setup/hold time, if this endpoint is clocked for (auto &fanin : pd.cell_arcs) { - printf("walk bwd %s.%s, fanin: %s, arctype: %s\n", ep.first.cell.c_str(ctx), - ep.first.port.c_str(ctx), fanin.other_port.c_str(ctx), arcType_to_str(fanin.type).c_str()); if (fanin.type == CellArc::SETUP && fanin.other_port == ep.second) { - if (clock_skew) { + if (with_clock_skew) { auto clock_delay = ports.at(CellPortKey(ep.first.cell, fanin.other_port)).route_delay; - init_required += clock_delay; + init_required -= DelayPair(clock_delay.min_delay); } init_required.min_delay -= fanin.value.maxDelay(); } @@ -695,6 +685,9 @@ dict TimingAnalyser::max_delay_by_domain_pairs() auto dp = domain_pair_id(launch, capture); delay_t delay = arr.second.value.maxDelay() - req.second.value.minDelay(); + printf("%s -> %s, arr: %f, req: %f, delay: %f\n", domains.at(launch).key.clock.c_str(ctx), + domains.at(capture).key.clock.c_str(ctx), ctx->getDelayNS(arr.second.value.maxDelay()), + ctx->getDelayNS(req.second.value.minDelay()), ctx->getDelayNS(delay)); if (!domain_delay.count(dp) || domain_delay.at(dp) < delay) domain_delay[dp] = delay; } @@ -825,9 +818,9 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, { CriticalPath report; - auto &dp = domain_pairs.at(domain_pair); - auto &launch = domains.at(dp.key.launch).key; - auto &capture = domains.at(dp.key.capture).key; + const auto &dp = domain_pairs.at(domain_pair); + const auto &launch = domains.at(dp.key.launch).key; + const auto &capture = domains.at(dp.key.capture).key; report.delay = DelayPair(0); @@ -851,27 +844,13 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, } } - // Set hold time - auto cell = cell_info(endpoint); - auto &port = port_info(endpoint); - int port_clocks; - auto portClass = ctx->getPortTimingClass(cell, port.name, port_clocks); - if (portClass == TMG_REGISTER_INPUT) { - for (int i = 0; i < port_clocks; i++) { - auto info = ctx->getPortClockingInfo(cell, port.name, i); - if (!cell->ports.count(info.clock_port) || cell->ports.at(info.clock_port).net == nullptr) - continue; - - report.bound.min_delay = info.hold.min_delay; - break; - } - } - + // Walk the min or max path backwards to find a single crit path pool> visited; std::vector crit_path_rev; auto cursor = endpoint; - while (cursor != CellPortKey()) { + bool is_startpoint = false; + do { auto cell = cell_info(cursor); auto &port = port_info(cursor); int port_clocks; @@ -881,7 +860,12 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, if (!visited.insert(std::make_pair(cell->name, port.name)).second) break; - if (portClass != TMG_CLOCK_INPUT && portClass != TMG_IGNORE && port.type == PortType::PORT_IN) + // We store the reversed critical path as all input ports that lead to + // the timing startpoint. + auto is_input = portClass != TMG_CLOCK_INPUT && portClass != TMG_IGNORE && port.type == PortType::PORT_IN; + is_startpoint = portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT; + + if (is_input || is_startpoint) crit_path_rev.emplace_back(PortRef{cell, port.name}); if (!ports.at(cursor).arrival.count(dp.key.launch)) @@ -892,32 +876,99 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, } else { cursor = ports.at(cursor).arrival.at(dp.key.launch).bwd_min; } - } + } while (!is_startpoint); auto crit_path = boost::adaptors::reverse(crit_path_rev); - auto &front = crit_path.front(); - auto &front_port = front.cell->ports.at(front.port); - auto &front_driver = front_port.net->driver; + // Get timing and clocking info on the startpoint + const auto &sp = crit_path.front(); + const auto &sp_cell = sp.cell; + const auto &sp_port = sp_cell->ports.at(sp.port); + int sp_clocks; + const auto sp_portClass = ctx->getPortTimingClass(sp_cell, sp_port.name, sp_clocks); + TimingClockingInfo sp_clk_info; + const NetInfo *sp_clk_net = nullptr; + bool register_start = sp_portClass == TMG_REGISTER_OUTPUT; - portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); - - const CellInfo *last_cell = front.cell; - IdString last_port = front_driver.port; - - int clock_start = -1; - if (portClass == TMG_REGISTER_OUTPUT) { - for (int i = 0; i < port_clocks; i++) { - TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i); - const NetInfo *clknet = front_driver.cell->getPort(clockInfo.clock_port); - if (clknet != nullptr && clknet->name == launch.clock && clockInfo.edge == launch.edge) { - last_port = clockInfo.clock_port; - clock_start = i; + if (register_start) { + // If we don't find a clock we don't consider this startpoint to be registered. + register_start = sp_clocks > 0; + for (int i = 0; i < sp_clocks; i++) { + sp_clk_info = ctx->getPortClockingInfo(sp_cell, sp_port.name, i); + const auto clk_net = sp_cell->getPort(sp_clk_info.clock_port); + register_start = clk_net != nullptr && clk_net->name == launch.clock && sp_clk_info.edge == launch.edge; + if (register_start) { + sp_clk_net = clk_net; break; } } } + // Get timing and clocking info on the endpoint + const auto &ep = crit_path.back(); + const auto &ep_cell = ep.cell; + const auto &ep_port = ep_cell->ports.at(ep.port); + int ep_clocks; + const auto ep_portClass = ctx->getPortTimingClass(ep_cell, ep_port.name, ep_clocks); + TimingClockingInfo ep_clk_info; + const NetInfo *ep_clk_net = nullptr; + + bool register_end = ep_portClass == TMG_REGISTER_INPUT; + + if (register_end) { + // If we don't find a clock we don't consider this startpoint to be registered. + register_end = ep_clocks > 0; + for (int i = 0; i < ep_clocks; i++) { + ep_clk_info = ctx->getPortClockingInfo(ep_cell, ep_port.name, i); + const auto clk_net = ep_cell->getPort(ep_clk_info.clock_port); + + register_end = clk_net != nullptr && clk_net->name == capture.clock && ep_clk_info.edge == capture.edge; + if (register_end) { + ep_clk_net = clk_net; + break; + } + } + } + + auto clock_pair = std::make_pair(launch.clock, capture.clock); + auto related_clock = clock_delays.count(clock_pair) > 0; + auto same_clock = launch.clock == capture.clock; + + delay_t skew = 0; + if (related_clock) { + skew -= clock_delays.at(clock_pair); + } + + // Calculate clock skew only if start- and endpoint are registered + // and either the clock domains are the same or related clock + if (with_clock_skew && register_start && register_end && (same_clock || related_clock)) { + + auto clock_delay_launch = ctx->getNetinfoRouteDelay(sp_clk_net, PortRef{sp_cell, sp_clk_info.clock_port}); + auto clock_delay_capture = ctx->getNetinfoRouteDelay(ep_clk_net, PortRef{ep_cell, ep_clk_info.clock_port}); + + printf("delay launch: %f\n", ctx->getDelayNS(clock_delay_launch)); + printf("delay capture: %f\n", ctx->getDelayNS(clock_delay_capture)); + printf("clock to clock: %f\n", ctx->getDelayNS(skew)); + skew += clock_delay_launch - clock_delay_capture; + } + + if (skew != 0) { + CriticalPath::Segment seg_skew; + seg_skew.type = CriticalPath::Segment::Type::CLK_SKEW; + seg_skew.delay = DelayPair(skew); + seg_skew.from = std::make_pair(sp_cell->name, sp_clk_info.clock_port); + seg_skew.to = std::make_pair(ep_cell->name, ep_clk_info.clock_port); + if (same_clock) { + seg_skew.net = launch.clock; + } else { + seg_skew.net = IdString(); + } + report.segments.push_back(seg_skew); + } + + const CellInfo *prev_cell = sp_cell; + IdString prev_port = sp_port.name; + for (auto sink : crit_path) { auto sink_cell = sink.cell; auto &port = sink_cell->ports.at(sink.port); @@ -928,22 +979,20 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, CriticalPath::Segment seg_logic; DelayQuad comb_delay; - if (clock_start != -1) { - auto clockInfo = ctx->getPortClockingInfo(driver_cell, driver.port, clock_start); - comb_delay = clockInfo.clockToQ; - clock_start = -1; + if (is_startpoint && register_start) { + comb_delay = sp_clk_info.clockToQ; seg_logic.type = CriticalPath::Segment::Type::CLK_TO_Q; - } else if (last_port == driver.port) { + } else if (prev_port == driver.port) { // Case where we start with a STARTPOINT etc comb_delay = DelayQuad(0); seg_logic.type = CriticalPath::Segment::Type::SOURCE; } else { - ctx->getCellDelay(driver_cell, last_port, driver.port, comb_delay); + ctx->getCellDelay(driver_cell, prev_port, driver.port, comb_delay); seg_logic.type = CriticalPath::Segment::Type::LOGIC; } seg_logic.delay = comb_delay.delayPair(); - seg_logic.from = std::make_pair(last_cell->name, last_port); + seg_logic.from = std::make_pair(prev_cell->name, prev_port); seg_logic.to = std::make_pair(driver_cell->name, driver.port); seg_logic.net = IdString(); report.segments.push_back(seg_logic); @@ -958,23 +1007,29 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, seg_route.net = net->name; report.segments.push_back(seg_route); - last_cell = sink_cell; - last_port = sink.port; + prev_cell = sink_cell; + prev_port = sink.port; + is_startpoint = false; } - int clockCount = 0; - auto sinkClass = ctx->getPortTimingClass(crit_path.back().cell, crit_path.back().port, clockCount); - if (sinkClass == TMG_REGISTER_INPUT && clockCount > 0) { - auto sinkClockInfo = ctx->getPortClockingInfo(crit_path.back().cell, crit_path.back().port, 0); - auto setup = sinkClockInfo.setup; - + // Add setup/hold time as final segment + // And add hold time as min bound + if (register_end) { CriticalPath::Segment seg_logic; - seg_logic.type = CriticalPath::Segment::Type::SETUP; - seg_logic.delay = setup; - seg_logic.from = std::make_pair(last_cell->name, last_port); + seg_logic.delay = DelayPair(0); + if (longest_path) { + seg_logic.type = CriticalPath::Segment::Type::SETUP; + seg_logic.delay += ep_clk_info.setup; + } else { + seg_logic.type = CriticalPath::Segment::Type::HOLD; + seg_logic.delay -= ep_clk_info.hold; + } + seg_logic.from = std::make_pair(prev_cell->name, prev_port); seg_logic.to = seg_logic.from; seg_logic.net = IdString(); report.segments.push_back(seg_logic); + + report.bound.min_delay = ep_clk_info.hold.min_delay; } return report; @@ -1116,17 +1171,6 @@ std::vector TimingAnalyser::get_min_delay_violations() const auto &capture = domains.at(capture_id); const auto &capture_clock = capture.key.clock; - if (ctx->nets.count(capture_clock) == 0) { - continue; - } - - NetInfo *clk_net = ctx->nets.at(capture_clock).get(); - if (clk_net->clkconstr == nullptr) { - continue; - } - - DelayPair period = clk_net->clkconstr->period; - for (auto &ep : capture.endpoints) { CellInfo *ci = cell_info(ep.first); int clkInfoCount = 0; @@ -1143,7 +1187,7 @@ std::vector TimingAnalyser::get_min_delay_violations() const auto &launch_clock = launch.key.clock; auto clocks = std::make_pair(launch_clock, capture_clock); - auto related_clocks = clock_delays.count(clocks) != 0; + auto related_clocks = clock_delays.count(clocks) > 0; // Don't consider clocks without known relationships if (launch_id != capture_id && !related_clocks) { @@ -1155,9 +1199,13 @@ std::vector TimingAnalyser::get_min_delay_violations() clock_to_clock = clock_delays.at(clocks); } - auto hold_slack = arr.value.minDelay() - req.value.maxDelay() + clock_to_clock; + auto hold_slack = arr.value.minDelay() + req.value.maxDelay() + clock_to_clock; + auto violated = hold_slack <= 0; - auto violated = true; + // printf("arr min: %f, req max: %f, c2c; %f, slack: %f, arr path len: %d, req path len: %d\n", + // ctx->getDelayNS(arr.value.minDelay()), ctx->getDelayNS(req.value.maxDelay()), + // ctx->getDelayNS(clock_to_clock), ctx->getDelayNS(hold_slack), arr.path_length, + // req.path_length); if (violated) { const auto dom_pair_id = domain_pair_id(launch_id, capture_id); @@ -1233,7 +1281,7 @@ void timing_analysis(Context *ctx, bool print_slack_histogram, bool print_fmax, { TimingAnalyser tmg(ctx); tmg.setup_only = false; - tmg.clock_skew = true; + tmg.with_clock_skew = false; tmg.setup(ctx->detailed_timing_report, print_slack_histogram, print_path || print_fmax); auto &result = tmg.get_timing_result(); diff --git a/common/kernel/timing.h b/common/kernel/timing.h index eea57700..fc60a5ee 100644 --- a/common/kernel/timing.h +++ b/common/kernel/timing.h @@ -27,8 +27,8 @@ NEXTPNR_NAMESPACE_BEGIN struct CellPortKey { - CellPortKey() {}; - CellPortKey(IdString cell, IdString port) : cell(cell), port(port) {}; + CellPortKey(){}; + CellPortKey(IdString cell, IdString port) : cell(cell), port(port){}; explicit CellPortKey(const PortRef &pr) { NPNR_ASSERT(pr.cell != nullptr); @@ -49,7 +49,7 @@ struct ClockDomainKey { IdString clock; ClockEdge edge; - ClockDomainKey(IdString clock_net, ClockEdge edge) : clock(clock_net), edge(edge) {}; + ClockDomainKey(IdString clock_net, ClockEdge edge) : clock(clock_net), edge(edge){}; // probably also need something here to deal with constraints inline bool is_async() const { return clock == IdString(); } @@ -63,7 +63,7 @@ typedef int domain_id_t; struct ClockDomainPairKey { domain_id_t launch, capture; - ClockDomainPairKey(domain_id_t launch, domain_id_t capture) : launch(launch), capture(capture) {}; + ClockDomainPairKey(domain_id_t launch, domain_id_t capture) : launch(launch), capture(capture){}; inline bool operator==(const ClockDomainPairKey &other) const { return (launch == other.launch) && (capture == other.capture); @@ -100,7 +100,7 @@ struct TimingAnalyser // Enable analysis of clock skew between FFs. // Only do this after legal placement - bool clock_skew = false; + bool with_clock_skew = true; bool setup_only = false; bool have_loops = false; @@ -180,9 +180,9 @@ struct TimingAnalyser ClockEdge edge; CellArc(ArcType type, IdString other_port, DelayQuad value) - : type(type), other_port(other_port), value(value), edge(RISING_EDGE) {}; + : type(type), other_port(other_port), value(value), edge(RISING_EDGE){}; CellArc(ArcType type, IdString other_port, DelayQuad value, ClockEdge edge) - : type(type), other_port(other_port), value(value), edge(edge) {}; + : type(type), other_port(other_port), value(value), edge(edge){}; }; // Timing data for every cell port @@ -206,7 +206,7 @@ struct TimingAnalyser struct PerDomain { - PerDomain(ClockDomainKey key) : key(key) {}; + PerDomain(ClockDomainKey key) : key(key){}; ClockDomainKey key; // these are pairs (signal port; clock port) std::vector> startpoints, endpoints; @@ -214,7 +214,7 @@ struct TimingAnalyser struct PerDomainPair { - PerDomainPair(ClockDomainPairKey key) : key(key) {}; + PerDomainPair(ClockDomainPairKey key) : key(key){}; ClockDomainPairKey key; DelayPair period{0}; delay_t worst_setup_slack, worst_hold_slack; diff --git a/common/kernel/timing_log.cc b/common/kernel/timing_log.cc index 349f2ba8..4de2fac7 100644 --- a/common/kernel/timing_log.cc +++ b/common/kernel/timing_log.cc @@ -82,7 +82,7 @@ static void log_crit_paths(const Context *ctx, TimingResult &result) return ctx->getDelayNS(d.maxDelay()); }; - log_info("curr total\n"); + log_info(" curr total type\n"); for (const auto &segment : path.segments) { total += segment.delay; @@ -90,14 +90,15 @@ static void log_crit_paths(const Context *ctx, TimingResult &result) if (segment.type == CriticalPath::Segment::Type::CLK_TO_Q || segment.type == CriticalPath::Segment::Type::SOURCE || segment.type == CriticalPath::Segment::Type::LOGIC || - segment.type == CriticalPath::Segment::Type::SETUP) { + segment.type == CriticalPath::Segment::Type::SETUP || + segment.type == CriticalPath::Segment::Type::HOLD) { logic_total += segment.delay; - const std::string type_name = (segment.type == CriticalPath::Segment::Type::SETUP) ? "Setup" : "Source"; - - log_info("%4.1f %4.1f %s %s.%s\n", get_delay_ns(segment.delay), get_delay_ns(total), type_name.c_str(), - segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); - } else if (segment.type == CriticalPath::Segment::Type::ROUTING) { + log_info("% 5.2f % 5.2f %s %s.%s\n", get_delay_ns(segment.delay), get_delay_ns(total), + CriticalPath::Segment::type_to_str(segment.type).c_str(), segment.to.first.c_str(ctx), + segment.to.second.c_str(ctx)); + } else if (segment.type == CriticalPath::Segment::Type::ROUTING || + segment.type == CriticalPath::Segment::Type::CLK_SKEW) { route_total = route_total + segment.delay; const auto &driver = ctx->cells.at(segment.from.first); @@ -106,7 +107,8 @@ static void log_crit_paths(const Context *ctx, TimingResult &result) auto driver_loc = ctx->getBelLocation(driver->bel); auto sink_loc = ctx->getBelLocation(sink->bel); - log_info("%4.1f %4.1f Net %s (%d,%d) -> (%d,%d)\n", get_delay_ns(segment.delay), get_delay_ns(total), + log_info("% 5.2f % 5.2f %s Net %s (%d,%d) -> (%d,%d)\n", get_delay_ns(segment.delay), + get_delay_ns(total), CriticalPath::Segment::type_to_str(segment.type).c_str(), segment.net.c_str(ctx), driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); log_info(" Sink %s.%s\n", segment.to.first.c_str(ctx), segment.to.second.c_str(ctx)); @@ -145,7 +147,7 @@ static void log_crit_paths(const Context *ctx, TimingResult &result) } } } - log_info("%.1f ns logic, %.1f ns routing\n", get_delay_ns(logic_total), get_delay_ns(route_total)); + log_info("%.2f ns logic, %.2f ns routing\n", get_delay_ns(logic_total), get_delay_ns(route_total)); }; // Single domain paths @@ -175,7 +177,7 @@ static void log_crit_paths(const Context *ctx, TimingResult &result) auto num_min_violations = result.min_delay_violations.size(); if (num_min_violations > 0) { log_break(); - log_info("Hold time violations:\n"); + log_info("Hold time/min time violation:\n"); for (size_t i = 0; i < std::min((size_t)10, num_min_violations); ++i) { auto &report = result.min_delay_violations.at(i); log_break();