| #include <cstdio> |
| #include <cstring> |
| #include <cmath> |
| #include <set> |
| |
| #include "vtr_assert.h" |
| #include "vtr_log.h" |
| #include "vtr_math.h" |
| #include "vtr_ndmatrix.h" |
| |
| #include "vpr_types.h" |
| #include "vpr_error.h" |
| |
| #include "globals.h" |
| #include "atom_netlist.h" |
| #include "rr_graph_area.h" |
| #include "segment_stats.h" |
| #include "channel_stats.h" |
| #include "stats.h" |
| #include "net_delay.h" |
| #include "read_xml_arch_file.h" |
| #include "echo_files.h" |
| |
| #include "timing_info.h" |
| #include "RoutingDelayCalculator.h" |
| |
| #include "timing_util.h" |
| #include "tatum/TimingReporter.hpp" |
| |
| /********************** Subroutines local to this module *********************/ |
| |
| static void load_channel_occupancies(vtr::Matrix<int>& chanx_occ, vtr::Matrix<int>& chany_occ); |
| |
| static void length_and_bends_stats(); |
| |
| static void get_channel_occupancy_stats(); |
| |
| /************************* Subroutine definitions ****************************/ |
| |
| void routing_stats(bool full_stats, enum e_route_type route_type, std::vector<t_segment_inf>& segment_inf, float R_minW_nmos, float R_minW_pmos, float grid_logic_tile_area, enum e_directionality directionality, int wire_to_ipin_switch) { |
| /* Prints out various statistics about the current routing. Both a routing * |
| * and an rr_graph must exist when you call this routine. */ |
| |
| float area, used_area; |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| int num_rr_switch = device_ctx.rr_switch_inf.size(); |
| |
| length_and_bends_stats(); |
| print_channel_stats(); |
| get_channel_occupancy_stats(); |
| |
| VTR_LOG("Logic area (in minimum width transistor areas, excludes I/Os and empty grid tiles)...\n"); |
| |
| area = 0; |
| for (size_t i = 0; i < device_ctx.grid.width(); i++) { |
| for (size_t j = 0; j < device_ctx.grid.height(); j++) { |
| auto type = device_ctx.grid[i][j].type; |
| if (device_ctx.grid[i][j].width_offset == 0 |
| && device_ctx.grid[i][j].height_offset == 0 |
| && !is_io_type(type) |
| && type != device_ctx.EMPTY_TYPE) { |
| if (type->area == UNDEFINED) { |
| area += grid_logic_tile_area * type->width * type->height; |
| } else { |
| area += type->area; |
| } |
| } |
| } |
| } |
| /* Todo: need to add pitch of routing to blocks with height > 3 */ |
| VTR_LOG("\tTotal logic block area (Warning, need to add pitch of routing to blocks with height > 3): %g\n", area); |
| |
| used_area = 0; |
| for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { |
| auto type = physical_tile_type(blk_id); |
| if (!is_io_type(type)) { |
| if (type->area == UNDEFINED) { |
| used_area += grid_logic_tile_area * type->width * type->height; |
| } else { |
| used_area += type->area; |
| } |
| } |
| } |
| VTR_LOG("\tTotal used logic block area: %g\n", used_area); |
| |
| if (route_type == DETAILED) { |
| count_routing_transistors(directionality, num_rr_switch, wire_to_ipin_switch, |
| segment_inf, R_minW_nmos, R_minW_pmos); |
| get_segment_usage_stats(segment_inf); |
| } |
| |
| if (full_stats == true) |
| print_wirelen_prob_dist(); |
| } |
| |
| /* Figures out maximum, minimum and average number of bends and net length * |
| * in the routing. */ |
| void length_and_bends_stats() { |
| int bends, total_bends, max_bends; |
| int length, total_length, max_length; |
| int segments, total_segments, max_segments; |
| float av_bends, av_length, av_segments; |
| int num_global_nets, num_clb_opins_reserved; |
| |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| max_bends = 0; |
| total_bends = 0; |
| max_length = 0; |
| total_length = 0; |
| max_segments = 0; |
| total_segments = 0; |
| num_global_nets = 0; |
| num_clb_opins_reserved = 0; |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| if (!cluster_ctx.clb_nlist.net_is_ignored(net_id) && cluster_ctx.clb_nlist.net_sinks(net_id).size() != 0) { /* Globals don't count. */ |
| get_num_bends_and_length(net_id, &bends, &length, &segments); |
| |
| total_bends += bends; |
| max_bends = std::max(bends, max_bends); |
| |
| total_length += length; |
| max_length = std::max(length, max_length); |
| |
| total_segments += segments; |
| max_segments = std::max(segments, max_segments); |
| } else if (cluster_ctx.clb_nlist.net_is_ignored(net_id)) { |
| num_global_nets++; |
| } else { |
| num_clb_opins_reserved++; |
| } |
| } |
| |
| av_bends = (float)total_bends / (float)((int)cluster_ctx.clb_nlist.nets().size() - num_global_nets); |
| VTR_LOG("\n"); |
| VTR_LOG("Average number of bends per net: %#g Maximum # of bends: %d\n", av_bends, max_bends); |
| VTR_LOG("\n"); |
| |
| av_length = (float)total_length / (float)((int)cluster_ctx.clb_nlist.nets().size() - num_global_nets); |
| VTR_LOG("Number of global nets: %d\n", num_global_nets); |
| VTR_LOG("Number of routed nets (nonglobal): %d\n", (int)cluster_ctx.clb_nlist.nets().size() - num_global_nets); |
| VTR_LOG("Wire length results (in units of 1 clb segments)...\n"); |
| VTR_LOG("\tTotal wirelength: %d, average net length: %#g\n", total_length, av_length); |
| VTR_LOG("\tMaximum net length: %d\n", max_length); |
| VTR_LOG("\n"); |
| |
| av_segments = (float)total_segments / (float)((int)cluster_ctx.clb_nlist.nets().size() - num_global_nets); |
| VTR_LOG("Wire length results in terms of physical segments...\n"); |
| VTR_LOG("\tTotal wiring segments used: %d, average wire segments per net: %#g\n", total_segments, av_segments); |
| VTR_LOG("\tMaximum segments used by a net: %d\n", max_segments); |
| VTR_LOG("\tTotal local nets with reserved CLB opins: %d\n", num_clb_opins_reserved); |
| } |
| |
| static void get_channel_occupancy_stats() { |
| /* Determines how many tracks are used in each channel. */ |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| auto chanx_occ = vtr::Matrix<int>({{ |
| device_ctx.grid.width(), //[0 .. device_ctx.grid.width() - 1] (length of x channel) |
| device_ctx.grid.height() - 1 //[0 .. device_ctx.grid.height() - 2] (# x channels) |
| }}, |
| 0); |
| |
| auto chany_occ = vtr::Matrix<int>({{ |
| device_ctx.grid.width() - 1, //[0 .. device_ctx.grid.width() - 2] (# y channels) |
| device_ctx.grid.height() //[0 .. device_ctx.grid.height() - 1] (length of y channel) |
| }}, |
| 0); |
| load_channel_occupancies(chanx_occ, chany_occ); |
| |
| VTR_LOG("\n"); |
| VTR_LOG("X - Directed channels: j max occ ave occ capacity\n"); |
| VTR_LOG(" ---- ------- ------- --------\n"); |
| |
| int total_x = 0; |
| for (size_t j = 0; j < device_ctx.grid.height() - 1; ++j) { |
| total_x += device_ctx.chan_width.x_list[j]; |
| float ave_occ = 0.0; |
| int max_occ = -1; |
| |
| for (size_t i = 1; i < device_ctx.grid.width(); ++i) { |
| max_occ = std::max(chanx_occ[i][j], max_occ); |
| ave_occ += chanx_occ[i][j]; |
| } |
| ave_occ /= device_ctx.grid.width(); |
| VTR_LOG(" %4d %7d %7.3f %8d\n", j, max_occ, ave_occ, device_ctx.chan_width.x_list[j]); |
| } |
| |
| VTR_LOG("Y - Directed channels: i max occ ave occ capacity\n"); |
| VTR_LOG(" ---- ------- ------- --------\n"); |
| |
| int total_y = 0; |
| for (size_t i = 0; i < device_ctx.grid.width() - 1; ++i) { |
| total_y += device_ctx.chan_width.y_list[i]; |
| float ave_occ = 0.0; |
| int max_occ = -1; |
| |
| for (size_t j = 1; j < device_ctx.grid.height(); ++j) { |
| max_occ = std::max(chany_occ[i][j], max_occ); |
| ave_occ += chany_occ[i][j]; |
| } |
| ave_occ /= device_ctx.grid.height(); |
| VTR_LOG(" %4d %7d %7.3f %8d\n", i, max_occ, ave_occ, device_ctx.chan_width.y_list[i]); |
| } |
| |
| VTR_LOG("\n"); |
| VTR_LOG("Total tracks in x-direction: %d, in y-direction: %d\n", total_x, total_y); |
| VTR_LOG("\n"); |
| } |
| |
| /* Loads the two arrays passed in with the total occupancy at each of the * |
| * channel segments in the FPGA. */ |
| static void load_channel_occupancies(vtr::Matrix<int>& chanx_occ, vtr::Matrix<int>& chany_occ) { |
| int i, j, inode; |
| t_trace* tptr; |
| t_rr_type rr_type; |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| /* First set the occupancy of everything to zero. */ |
| chanx_occ.fill(0); |
| chany_occ.fill(0); |
| |
| /* Now go through each net and count the tracks and pins used everywhere */ |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| /* Skip global and empty nets. */ |
| if (cluster_ctx.clb_nlist.net_is_ignored(net_id) && cluster_ctx.clb_nlist.net_sinks(net_id).size() != 0) |
| continue; |
| |
| tptr = route_ctx.trace[net_id].head; |
| while (tptr != nullptr) { |
| inode = tptr->index; |
| rr_type = device_ctx.rr_nodes[inode].type(); |
| |
| if (rr_type == SINK) { |
| tptr = tptr->next; /* Skip next segment. */ |
| if (tptr == nullptr) |
| break; |
| } |
| |
| else if (rr_type == CHANX) { |
| j = device_ctx.rr_nodes[inode].ylow(); |
| for (i = device_ctx.rr_nodes[inode].xlow(); i <= device_ctx.rr_nodes[inode].xhigh(); i++) |
| chanx_occ[i][j]++; |
| } |
| |
| else if (rr_type == CHANY) { |
| i = device_ctx.rr_nodes[inode].xlow(); |
| for (j = device_ctx.rr_nodes[inode].ylow(); j <= device_ctx.rr_nodes[inode].yhigh(); j++) |
| chany_occ[i][j]++; |
| } |
| |
| tptr = tptr->next; |
| } |
| } |
| } |
| |
| void get_num_bends_and_length(ClusterNetId inet, int* bends_ptr, int* len_ptr, int* segments_ptr) { |
| /* Counts and returns the number of bends, wirelength, and number of routing * |
| * resource segments in net inet's routing. */ |
| auto& route_ctx = g_vpr_ctx.routing(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| t_trace *tptr, *prevptr; |
| int inode; |
| t_rr_type curr_type, prev_type; |
| int bends, length, segments; |
| |
| bends = 0; |
| length = 0; |
| segments = 0; |
| |
| prevptr = route_ctx.trace[inet].head; /* Should always be SOURCE. */ |
| if (prevptr == nullptr) { |
| VPR_FATAL_ERROR(VPR_ERROR_OTHER, |
| "in get_num_bends_and_length: net #%lu has no traceback.\n", size_t(inet)); |
| } |
| inode = prevptr->index; |
| prev_type = device_ctx.rr_nodes[inode].type(); |
| |
| tptr = prevptr->next; |
| |
| while (tptr != nullptr) { |
| inode = tptr->index; |
| curr_type = device_ctx.rr_nodes[inode].type(); |
| |
| if (curr_type == SINK) { /* Starting a new segment */ |
| tptr = tptr->next; /* Link to existing path - don't add to len. */ |
| if (tptr == nullptr) |
| break; |
| |
| curr_type = device_ctx.rr_nodes[tptr->index].type(); |
| } |
| |
| else if (curr_type == CHANX || curr_type == CHANY) { |
| segments++; |
| length += 1 + device_ctx.rr_nodes[inode].xhigh() - device_ctx.rr_nodes[inode].xlow() |
| + device_ctx.rr_nodes[inode].yhigh() - device_ctx.rr_nodes[inode].ylow(); |
| |
| if (curr_type != prev_type && (prev_type == CHANX || prev_type == CHANY)) |
| bends++; |
| } |
| |
| prev_type = curr_type; |
| tptr = tptr->next; |
| } |
| |
| *bends_ptr = bends; |
| *len_ptr = length; |
| *segments_ptr = segments; |
| } |
| |
| void print_wirelen_prob_dist() { |
| /* Prints out the probability distribution of the wirelength / number * |
| * input pins on a net -- i.e. simulates 2-point net length probability * |
| * distribution. */ |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| float* prob_dist; |
| float norm_fac, two_point_length; |
| int bends, length, segments, index; |
| float av_length; |
| int prob_dist_size, i, incr; |
| |
| prob_dist_size = device_ctx.grid.width() + device_ctx.grid.height() + 10; |
| prob_dist = (float*)vtr::calloc(prob_dist_size, sizeof(float)); |
| norm_fac = 0.; |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| if (!cluster_ctx.clb_nlist.net_is_ignored(net_id) && cluster_ctx.clb_nlist.net_sinks(net_id).size() != 0) { |
| get_num_bends_and_length(net_id, &bends, &length, &segments); |
| |
| /* Assign probability to two integer lengths proportionately -- i.e. * |
| * if two_point_length = 1.9, add 0.9 of the pins to prob_dist[2] and * |
| * only 0.1 to prob_dist[1]. */ |
| |
| int num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); |
| VTR_ASSERT(num_sinks > 0); |
| |
| two_point_length = (float)length / (float)(num_sinks); |
| index = (int)two_point_length; |
| if (index >= prob_dist_size) { |
| VTR_LOG_WARN("Index (%d) to prob_dist exceeds its allocated size (%d).\n", |
| index, prob_dist_size); |
| VTR_LOG("Realloc'ing to increase 2-pin wirelen prob distribution array.\n"); |
| incr = index - prob_dist_size + 2; |
| prob_dist_size += incr; |
| prob_dist = (float*)vtr::realloc(prob_dist, prob_dist_size * sizeof(float)); |
| for (i = prob_dist_size - incr; i < prob_dist_size; i++) |
| prob_dist[i] = 0.0; |
| } |
| prob_dist[index] += (num_sinks) * (1 - two_point_length + index); |
| |
| index++; |
| if (index >= prob_dist_size) { |
| VTR_LOG_WARN("Index (%d) to prob_dist exceeds its allocated size (%d).\n", |
| index, prob_dist_size); |
| VTR_LOG("Realloc'ing to increase 2-pin wirelen prob distribution array.\n"); |
| incr = index - prob_dist_size + 2; |
| prob_dist_size += incr; |
| prob_dist = (float*)vtr::realloc(prob_dist, |
| prob_dist_size * sizeof(float)); |
| for (i = prob_dist_size - incr; i < prob_dist_size; i++) |
| prob_dist[i] = 0.0; |
| } |
| prob_dist[index] += (num_sinks) * (1 - index + two_point_length); |
| |
| norm_fac += num_sinks; |
| } |
| } |
| |
| /* Normalize so total probability is 1 and print out. */ |
| |
| VTR_LOG("\n"); |
| VTR_LOG("Probability distribution of 2-pin net lengths:\n"); |
| VTR_LOG("\n"); |
| VTR_LOG("Length p(Lenth)\n"); |
| |
| av_length = 0; |
| |
| for (index = 0; index < prob_dist_size; index++) { |
| prob_dist[index] /= norm_fac; |
| VTR_LOG("%6d %10.6f\n", index, prob_dist[index]); |
| av_length += prob_dist[index] * index; |
| } |
| |
| VTR_LOG("\n"); |
| VTR_LOG("Number of 2-pin nets: ;%g;\n", norm_fac); |
| VTR_LOG("Expected value of 2-pin net length (R): ;%g;\n", av_length); |
| VTR_LOG("Total wirelength: ;%g;\n", norm_fac * av_length); |
| |
| free(prob_dist); |
| } |
| |
| void print_lambda() { |
| /* Finds the average number of input pins used per clb. Does not * |
| * count inputs which are hooked to global nets (i.e. the clock * |
| * when it is marked global). */ |
| |
| int ipin, iclass; |
| int num_inputs_used = 0; |
| float lambda; |
| |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { |
| auto type = physical_tile_type(blk_id); |
| VTR_ASSERT(type != nullptr); |
| if (!is_io_type(type)) { |
| for (ipin = 0; ipin < type->num_pins; ipin++) { |
| iclass = type->pin_class[ipin]; |
| if (type->class_inf[iclass].type == RECEIVER) { |
| ClusterNetId net_id = cluster_ctx.clb_nlist.block_net(blk_id, ipin); |
| if (net_id != ClusterNetId::INVALID()) /* Pin is connected? */ |
| if (!cluster_ctx.clb_nlist.net_is_ignored(net_id)) /* Not a global clock */ |
| num_inputs_used++; |
| } |
| } |
| } |
| } |
| |
| lambda = (float)num_inputs_used / (float)cluster_ctx.clb_nlist.blocks().size(); |
| VTR_LOG("Average lambda (input pins used per clb) is: %g\n", lambda); |
| } |
| |
| int count_netlist_clocks() { |
| /* Count how many clocks are in the netlist. */ |
| |
| auto& atom_ctx = g_vpr_ctx.atom(); |
| |
| std::set<std::string> clock_names; |
| |
| //Loop through each clock pin and record the names in clock_names |
| for (auto blk_id : atom_ctx.nlist.blocks()) { |
| for (auto pin_id : atom_ctx.nlist.block_clock_pins(blk_id)) { |
| auto net_id = atom_ctx.nlist.pin_net(pin_id); |
| clock_names.insert(atom_ctx.nlist.net_name(net_id)); |
| } |
| } |
| |
| //Since std::set does not include duplicates, the number of clocks is the size of the set |
| return static_cast<int>(clock_names.size()); |
| } |