Skip to content

Commit aabed10

Browse files
authored
Merge pull request #3023 from verilog-to-routing/routing_path_timing
[Analysis] Printing Nets timing Information
2 parents 091314c + 33291d3 commit aabed10

File tree

10 files changed

+249
-4
lines changed

10 files changed

+249
-4
lines changed

doc/src/vpr/command_line_usage.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,35 @@ VPR uses a negotiated congestion algorithm (based on Pathfinder) to perform rout
15171517
* `swns` - setup Worst Negative Slack (sWNS) [ns]
15181518
* `stns` - Setup Total Negative Slack (sTNS) [ns]
15191519

1520+
1521+
.. option:: --generate_net_timing_report {on | off}
1522+
1523+
Generates a report that lists the bounding box, slack, and delay of every routed connection in a design in CSV format (``report_net_timing.csv``). Each row in the CSV corresponds to a single net.
1524+
1525+
The report can later be used by other tools to enable further optimizations. For example, the Synopsys synthesis tool (Synplify) can use this information to re-synthesize the design and improve the Quality of Results (QoR).
1526+
1527+
Fields in the report are:
1528+
1529+
.. code-block:: none
1530+
1531+
netname : The name assigned to the net in the atom netlist
1532+
Fanout : Net's fanout (number of sinks)
1533+
bb_xmin : X coordinate of the net's bounding box's bottom-left corner
1534+
bb_ymin : Y coordinate of the net's bounding box's bottom-left corner
1535+
bb_layer_min : Lowest layer number of the net's bounding box
1536+
bb_xmax : X coordinate of the net's bounding box's top-right corner
1537+
bb_ymax : Y coordinate of the net's bounding box's top-right corner
1538+
bb_layer_max : Highest layer number of the net's bounding box
1539+
src_pin_name : Name of the net's source pin
1540+
src_pin_slack : Setup slack of the net's source pin
1541+
sinks : A semicolon-separated list of sink pin entries, each in the format:
1542+
<sink_pin_name>,<sink_pin_slack>,<sink_pin_delay>
1543+
1544+
Example value for the ``sinks`` field:
1545+
``"U2.B,0.12,0.5;U3.C,0.10,0.6;U4.D,0.08,0.7"``
1546+
1547+
**Default:** ``off``
1548+
15201549
.. option:: --route_verbosity <int>
15211550

15221551
Controls the verbosity of routing output.

vpr/src/analysis/timing_reports.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
#include "timing_reports.h"
22

3+
#include <fstream>
4+
#include <sstream>
5+
6+
#include "timing_reports.h"
7+
#include "rr_graph.h"
8+
39
#include "tatum/TimingReporter.hpp"
410

11+
#include "vtr_version.h"
512
#include "vpr_types.h"
613
#include "globals.h"
714

@@ -10,6 +17,117 @@
1017

1118
#include "VprTimingGraphResolver.h"
1219

20+
/**
21+
* @brief Get the bounding box of a routed net.
22+
* If the net is completely absorbed into a cluster block, return the bounding box of the cluster block.
23+
* Otherwise, return the bounding box of the net's route tree.
24+
*
25+
* @param atom_net_id The id of the atom net to get the bounding box of.
26+
*
27+
* @return The bounding box of the net. If the net is not routed, a bounding box
28+
* is returned with default values (OPEN).
29+
*/
30+
static t_bb get_net_bounding_box(const AtomNetId atom_net_id) {
31+
const auto& route_trees = g_vpr_ctx.routing().route_trees;
32+
const auto& rr_graph = g_vpr_ctx.device().rr_graph;
33+
34+
// Lambda to get the bounding box of a route tree
35+
auto route_tree_bb = [&](const RouteTree& route_tree) {
36+
t_bb bb;
37+
38+
// Set the initial bounding box to the root node's location
39+
RRNodeId route_tree_root = route_tree.root().inode;
40+
bb.xmin = rr_graph.node_xlow(route_tree_root);
41+
bb.xmax = rr_graph.node_xhigh(route_tree_root);
42+
bb.ymin = rr_graph.node_ylow(route_tree_root);
43+
bb.ymax = rr_graph.node_yhigh(route_tree_root);
44+
bb.layer_min = rr_graph.node_layer(route_tree_root);
45+
bb.layer_max = rr_graph.node_layer(route_tree_root);
46+
47+
// Iterate over all nodes in the route tree and update the bounding box
48+
for (auto& rt_node : route_tree.all_nodes()) {
49+
RRNodeId inode = rt_node.inode;
50+
51+
bb.xmin = std::min(static_cast<int>(rr_graph.node_xlow(inode)), bb.xmin);
52+
bb.xmax = std::max(static_cast<int>(rr_graph.node_xhigh(inode)), bb.xmax);
53+
54+
bb.ymin = std::min(static_cast<int>(rr_graph.node_ylow(inode)), bb.ymin);
55+
bb.ymax = std::max(static_cast<int>(rr_graph.node_yhigh(inode)), bb.ymax);
56+
57+
bb.layer_min = std::min(static_cast<int>(rr_graph.node_layer(inode)), bb.layer_min);
58+
bb.layer_max = std::max(static_cast<int>(rr_graph.node_layer(inode)), bb.layer_max);
59+
}
60+
return bb;
61+
};
62+
63+
if (g_vpr_ctx.routing().is_flat) {
64+
// If flat router is used, route tree data structure can be used
65+
// directly to get the bounding box of the net
66+
const auto& route_tree = route_trees[atom_net_id];
67+
if (!route_tree)
68+
return t_bb();
69+
return route_tree_bb(*route_tree);
70+
} else {
71+
// If two-stage router is used, we need to first get the cluster net id
72+
// corresponding to the atom net and then get the bounding box of the net
73+
// from the route tree. If the net is completely absorbed into a cluster block,
74+
const auto& atom_lookup = g_vpr_ctx.atom().lookup();
75+
const auto& cluster_net_id = atom_lookup.clb_nets(atom_net_id);
76+
std::vector<t_bb> bbs;
77+
t_bb max_bb;
78+
// There maybe multiple cluster nets corresponding to a single atom net.
79+
// We iterate over all cluster nets and the final bounding box is the union
80+
// of all cluster net bounding boxes
81+
if (cluster_net_id != vtr::nullopt) {
82+
for (const auto& clb_net_id : *cluster_net_id) {
83+
const auto& route_tree = route_trees[clb_net_id];
84+
if (!route_tree)
85+
continue;
86+
bbs.push_back(route_tree_bb(*route_tree));
87+
}
88+
if (bbs.empty()) {
89+
return t_bb();
90+
}
91+
// Assign the first cluster net's bounding box to the final bounding box
92+
// and then iteratively update it with the union of bounding boxes of
93+
// all cluster nets
94+
max_bb = bbs[0];
95+
for (size_t i = 1; i < bbs.size(); ++i) {
96+
max_bb.xmin = std::min(bbs[i].xmin, max_bb.xmin);
97+
max_bb.xmax = std::max(bbs[i].xmax, max_bb.xmax);
98+
max_bb.ymin = std::min(bbs[i].ymin, max_bb.ymin);
99+
max_bb.ymax = std::max(bbs[i].ymax, max_bb.ymax);
100+
max_bb.layer_min = std::min(bbs[i].layer_min, max_bb.layer_min);
101+
max_bb.layer_max = std::max(bbs[i].layer_max, max_bb.layer_max);
102+
}
103+
return max_bb;
104+
} else {
105+
// If there is no cluster net corresponding to the atom net,
106+
// it means the net is completely absorbed into a cluster block.
107+
// In that case, we set the bounding box the cluster block's bounding box
108+
const auto& atom_ctx = g_vpr_ctx.atom();
109+
const auto& atom_nlist = atom_ctx.netlist();
110+
AtomPinId source_pin = atom_nlist.net_driver(atom_net_id);
111+
112+
AtomBlockId atom_block = atom_nlist.pin_block(source_pin);
113+
VTR_ASSERT(atom_block != AtomBlockId::INVALID());
114+
ClusterBlockId cluster_block = atom_lookup.atom_clb(atom_block);
115+
VTR_ASSERT(cluster_block != ClusterBlockId::INVALID());
116+
117+
const t_pl_loc& cluster_block_loc = g_vpr_ctx.placement().block_locs()[cluster_block].loc;
118+
const auto& grid = g_vpr_ctx.device().grid;
119+
vtr::Rect<int> tile_bb = grid.get_tile_bb({cluster_block_loc.x, cluster_block_loc.y, cluster_block_loc.layer});
120+
const int block_layer = cluster_block_loc.layer;
121+
return t_bb(tile_bb.xmin(),
122+
tile_bb.xmax(),
123+
tile_bb.ymin(),
124+
tile_bb.ymax(),
125+
block_layer,
126+
block_layer);
127+
}
128+
}
129+
}
130+
13131
void generate_setup_timing_stats(const std::string& prefix,
14132
const SetupTimingInfo& timing_info,
15133
const AnalysisDelayCalculator& delay_calc,
@@ -61,3 +179,55 @@ void generate_hold_timing_stats(const std::string& prefix,
61179

62180
timing_reporter.report_unconstrained_hold(prefix + "report_unconstrained_timing.hold.rpt", *timing_info.hold_analyzer());
63181
}
182+
183+
void generate_net_timing_report(const std::string& prefix,
184+
const SetupHoldTimingInfo& timing_info,
185+
const AnalysisDelayCalculator& delay_calc) {
186+
std::ofstream os(prefix + "report_net_timing.csv");
187+
const auto& atom_netlist = g_vpr_ctx.atom().netlist();
188+
const auto& atom_lookup = g_vpr_ctx.atom().lookup();
189+
const auto& timing_ctx = g_vpr_ctx.timing();
190+
const auto& timing_graph = timing_ctx.graph;
191+
192+
// Write CSV header
193+
os << "netname,Fanout,bb_xmin,bb_ymin,bb_layer_min,"
194+
<< "bb_xmax,bb_ymax,bb_layer_max,"
195+
<< "src_pin_name,src_pin_slack,sinks" << std::endl;
196+
197+
for (const auto& net : atom_netlist.nets()) {
198+
const auto& net_name = atom_netlist.net_name(net);
199+
const auto& source_pin = *atom_netlist.net_pins(net).begin();
200+
// for the driver/source, this is the worst slack to any fanout.
201+
auto source_pin_slack = timing_info.setup_pin_slack(source_pin);
202+
auto tg_source_node = atom_lookup.atom_pin_tnode(source_pin);
203+
VTR_ASSERT(tg_source_node.is_valid());
204+
205+
const size_t fanout = atom_netlist.net_sinks(net).size();
206+
const auto& net_bb = get_net_bounding_box(net);
207+
208+
os << "\"" << net_name << "\"," // netname (quoted for safety)
209+
<< fanout << ","
210+
<< net_bb.xmin << "," << net_bb.ymin << "," << net_bb.layer_min << ","
211+
<< net_bb.xmax << "," << net_bb.ymax << "," << net_bb.layer_max << ","
212+
<< "\"" << atom_netlist.pin_name(source_pin) << "\"," << source_pin_slack << ",";
213+
214+
// Write sinks column (quoted, semicolon-delimited, each sink: name,slack,delay)
215+
os << "\"";
216+
for (size_t i = 0; i < fanout; ++i) {
217+
const auto& pin = *(atom_netlist.net_pins(net).begin() + i + 1);
218+
auto tg_sink_node = atom_lookup.atom_pin_tnode(pin);
219+
VTR_ASSERT(tg_sink_node.is_valid());
220+
221+
auto tg_edge_id = timing_graph->find_edge(tg_source_node, tg_sink_node);
222+
VTR_ASSERT(tg_edge_id.is_valid());
223+
224+
auto pin_setup_slack = timing_info.setup_pin_slack(pin);
225+
auto pin_delay = delay_calc.max_edge_delay(*timing_graph, tg_edge_id);
226+
const auto& pin_name = atom_netlist.pin_name(pin);
227+
228+
os << pin_name << "," << pin_setup_slack << "," << pin_delay;
229+
if (i != fanout - 1) os << ";";
230+
}
231+
os << "\"" << std::endl; // Close quoted sinks field and finish the row
232+
}
233+
}

vpr/src/analysis/timing_reports.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,29 @@ void generate_hold_timing_stats(const std::string& prefix,
2121
bool is_flat,
2222
const BlkLocRegistry& blk_loc_registry);
2323

24+
/**
25+
* @brief Generates a CSV report of timing information for each net in the atom netlist.
26+
*
27+
* Each row in the CSV corresponds to a single net and includes:
28+
* - Net name
29+
* - Fanout count
30+
* - Bounding box (xmin, ymin, layer_min, xmax, ymax, layer_max)
31+
* - Source pin name and slack
32+
* - A single "sinks" field that encodes information for all sink pins
33+
*
34+
* The "sinks" field is a semicolon-separated list of all sink pins.
35+
* Each sink pin is represented as a comma-separated triple:
36+
* <sink_pin_name>,<sink_pin_slack>,<sink_pin_delay>
37+
*
38+
* Example row:
39+
* netA,2,0,0,0,5,5,1,U1.A,0.25,"U2.B,0.12,0.5;U3.C,0.10,0.6"
40+
*
41+
* @param prefix Prefix for the output file name (report will be saved as <prefix>report_net_timing.csv)
42+
* @param timing_info Timing analysis results (slacks)
43+
* @param delay_calc Delay calculator used to extract delay between nodes
44+
*/
45+
void generate_net_timing_report(const std::string& prefix,
46+
const SetupHoldTimingInfo& timing_info,
47+
const AnalysisDelayCalculator& delay_calc);
48+
2449
#endif

vpr/src/base/SetupVPR.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysi
720720

721721
analysis_opts.timing_update_type = Options.timing_update_type;
722722
analysis_opts.write_timing_summary = Options.write_timing_summary;
723+
analysis_opts.generate_net_timing_report = Options.generate_net_timing_report;
723724
}
724725

725726
static void SetupPowerOpts(const t_options& Options, t_power_opts* power_opts, t_arch* Arch) {

vpr/src/base/read_options.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3088,6 +3088,14 @@ argparse::ArgumentParser create_arg_parser(const std::string& prog_name, t_optio
30883088
.help("Writes implemented design final timing summary to the specified JSON, XML or TXT file.")
30893089
.show_in(argparse::ShowIn::HELP_ONLY);
30903090

3091+
analysis_grp.add_argument<bool, ParseOnOff>(args.generate_net_timing_report, "--generate_net_timing_report")
3092+
.help(
3093+
"Generates a net timing report in CSV format, reporting the delay and slack\n"
3094+
"for every routed connection in the design.\n"
3095+
"The report is saved as 'report_net_timing.csv'.")
3096+
.default_value("off")
3097+
.show_in(argparse::ShowIn::HELP_ONLY);
3098+
30913099
auto& power_grp = parser.add_argument_group("power analysis options");
30923100

30933101
power_grp.add_argument<bool, ParseOnOff>(args.do_power, "--power")

vpr/src/base/read_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ struct t_options {
272272
argparse::ArgValue<e_post_synth_netlist_unconn_handling> post_synth_netlist_unconn_output_handling;
273273
argparse::ArgValue<bool> post_synth_netlist_module_parameters;
274274
argparse::ArgValue<std::string> write_timing_summary;
275+
argparse::ArgValue<bool> generate_net_timing_report;
275276
};
276277

277278
argparse::ArgumentParser create_arg_parser(const std::string& prog_name, t_options& args);

vpr/src/base/vpr_api.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,10 @@ void vpr_analysis(const Netlist<>& net_list,
14751475
merged_netlist_writer(atom_ctx.netlist().netlist_name(), analysis_delay_calc, Arch.models, vpr_setup.AnalysisOpts);
14761476
}
14771477

1478+
if (vpr_setup.AnalysisOpts.generate_net_timing_report) {
1479+
generate_net_timing_report(/*prefix=*/"", *timing_info, *analysis_delay_calc);
1480+
}
1481+
14781482
//Do power analysis
14791483
// TODO: Still assumes that cluster net list is used
14801484
if (vpr_setup.PowerOpts.do_power) {

vpr/src/base/vpr_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@ struct t_analysis_opts {
13561356
bool timing_report_skew;
13571357
std::string echo_dot_timing_graph_node;
13581358
std::string write_timing_summary;
1359+
bool generate_net_timing_report;
13591360

13601361
e_timing_update_type timing_update_type;
13611362
};

vtr_flow/tasks/regression_tests/vtr_reg_strong/strong_timing_report_detail/config/config.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ qor_parse_file=qor_standard.txt
2424
pass_requirements_file=pass_requirements.txt
2525

2626
# Script parameters
27-
script_params_common = -starting_stage vpr
27+
script_params_common = -starting_stage vpr --generate_net_timing_report on
2828
script_params_list_add=--timing_report_detail netlist
2929
script_params_list_add=--timing_report_detail aggregated
3030
script_params_list_add=--timing_report_detail detailed
31+
script_params_list_add=--timing_report_detail netlist --flat_routing on
32+
script_params_list_add=--timing_report_detail aggregated --flat_routing on
33+
script_params_list_add=--timing_report_detail detailed --flat_routing on

0 commit comments

Comments
 (0)