| /* Read in the .route file generated by VPR. |
| * This is done in order to perform timing analysis only using the |
| * --analysis option. This file set up the routing tree. It also checks that |
| * routing resource nodes and placement is consistent with the |
| * .net, .place, or the routing resource graph files. It is assumed |
| * that the .route file's formatting matches those as described in the |
| * vtr documentation. For example, the coordinates is assumed to be |
| * in (x,y) format. Appropriate error messages are displayed when |
| * formats are incorrect or when the routing file does not match |
| * other file's information*/ |
| |
| #include <iostream> |
| #include <fstream> |
| #include <cstdio> |
| #include <cstring> |
| #include <ctime> |
| #include <sstream> |
| #include <string> |
| #include <unordered_set> |
| |
| #include "atom_netlist.h" |
| #include "atom_netlist_utils.h" |
| #include "rr_graph.h" |
| #include "vtr_assert.h" |
| #include "vtr_util.h" |
| #include "tatum/echo_writer.hpp" |
| #include "vtr_log.h" |
| #include "check_route.h" |
| #include "route_common.h" |
| #include "vpr_types.h" |
| #include "globals.h" |
| #include "vpr_api.h" |
| #include "read_place.h" |
| #include "vpr_types.h" |
| #include "vpr_utils.h" |
| #include "vpr_error.h" |
| #include "place_and_route.h" |
| #include "timing_place.h" |
| #include "route_export.h" |
| #include "echo_files.h" |
| #include "route_common.h" |
| #include "read_route.h" |
| |
| /*************Functions local to this module*************/ |
| static void process_route(std::ifstream& fp, const char* filename, int& lineno); |
| static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* filename, int& lineno); |
| static void process_nets(std::ifstream& fp, ClusterNetId inet, std::string name, std::vector<std::string> input_tokens, const char* filename, int& lineno); |
| static void process_global_blocks(std::ifstream& fp, ClusterNetId inet, const char* filename, int& lineno); |
| static void format_coordinates(int& x, int& y, std::string coord, ClusterNetId net, const char* filename, const int lineno); |
| static void format_pin_info(std::string& pb_name, std::string& port_name, int& pb_pin_num, std::string input); |
| static std::string format_name(std::string name); |
| |
| /*************Global Functions****************************/ |
| bool read_route(const char* route_file, const t_router_opts& router_opts, bool verify_file_digests) { |
| /* Reads in the routing file to fill in the trace.head and t_clb_opins_used data structure. |
| * Perform a series of verification tests to ensure the netlist, placement, and routing |
| * files match */ |
| auto& device_ctx = g_vpr_ctx.mutable_device(); |
| auto& place_ctx = g_vpr_ctx.placement(); |
| /* Begin parsing the file */ |
| VTR_LOG("Begin loading FPGA routing file.\n"); |
| |
| std::string header_str; |
| |
| std::ifstream fp; |
| fp.open(route_file); |
| |
| int lineno = 0; |
| |
| if (!fp.is_open()) { |
| vpr_throw(VPR_ERROR_ROUTE, route_file, lineno, |
| "Cannot open %s routing file", route_file); |
| } |
| |
| std::getline(fp, header_str); |
| ++lineno; |
| |
| std::vector<std::string> header = vtr::split(header_str); |
| if (header[0] == "Placement_File:" && header[2] == "Placement_ID:" && header[3] != place_ctx.placement_id) { |
| auto msg = vtr::string_fmt( |
| "Placement file %s specified in the routing file" |
| " does not match the loaded placement (ID %s != %s)", |
| header[1].c_str(), header[3].c_str(), place_ctx.placement_id.c_str()); |
| if (verify_file_digests) { |
| vpr_throw(VPR_ERROR_ROUTE, route_file, lineno, msg.c_str()); |
| } else { |
| VTR_LOGF_WARN(route_file, lineno, "%s\n", msg.c_str()); |
| } |
| } |
| |
| /*Allocate necessary routing structures*/ |
| alloc_and_load_rr_node_route_structs(); |
| init_route_structs(router_opts.bb_factor); |
| |
| /*Check dimensions*/ |
| std::getline(fp, header_str); |
| ++lineno; |
| header.clear(); |
| header = vtr::split(header_str); |
| if (header[0] == "Array" && header[1] == "size:" && (vtr::atou(header[2].c_str()) != device_ctx.grid.width() || vtr::atou(header[4].c_str()) != device_ctx.grid.height())) { |
| vpr_throw(VPR_ERROR_ROUTE, route_file, lineno, |
| "Device dimensions %sx%s specified in the routing file does not match given %dx%d ", |
| header[2].c_str(), header[4].c_str(), device_ctx.grid.width(), device_ctx.grid.height()); |
| } |
| |
| /* Read in every net */ |
| process_route(fp, route_file, lineno); |
| |
| fp.close(); |
| |
| /*Correctly set up the clb opins*/ |
| recompute_occupancy_from_scratch(); |
| |
| /* Note: This pres_fac is not necessarily correct since it isn't the first routing iteration*/ |
| pathfinder_update_cost(router_opts.initial_pres_fac, router_opts.acc_fac); |
| |
| reserve_locally_used_opins(router_opts.initial_pres_fac, |
| router_opts.acc_fac, true); |
| |
| /* Finished loading in the routing, now check it*/ |
| recompute_occupancy_from_scratch(); |
| bool is_feasible = feasible_routing(); |
| |
| VTR_LOG("Finished loading route file\n"); |
| |
| return is_feasible; |
| } |
| |
| static void process_route(std::ifstream& fp, const char* filename, int& lineno) { |
| /*Walks through every net and add the routing appropriately*/ |
| std::string input; |
| std::vector<std::string> tokens; |
| while (std::getline(fp, input)) { |
| ++lineno; |
| tokens.clear(); |
| tokens = vtr::split(input); |
| if (tokens.empty()) { |
| continue; //Skip blank lines |
| } else if (tokens[0][0] == '#') { |
| continue; //Skip commented lines |
| } else if (tokens[0] == "Net") { |
| ClusterNetId inet(atoi(tokens[1].c_str())); |
| process_nets(fp, inet, tokens[2], tokens, filename, lineno); |
| } |
| } |
| |
| tokens.clear(); |
| } |
| |
| static void process_nets(std::ifstream& fp, ClusterNetId inet, std::string name, std::vector<std::string> input_tokens, const char* filename, int& lineno) { |
| /* Check if the net is global or not, and process appropriately */ |
| auto& cluster_ctx = g_vpr_ctx.mutable_clustering(); |
| |
| if (input_tokens.size() > 3 && input_tokens[3] == "global" |
| && input_tokens[4] == "net" && input_tokens[5] == "connecting:") { |
| /* Global net. Never routed. */ |
| if (!cluster_ctx.clb_nlist.net_is_ignored(inet)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net %lu should be a global net", size_t(inet)); |
| } |
| /*erase an extra colon for global nets*/ |
| name.erase(name.end() - 1); |
| name = format_name(name); |
| |
| if (0 != cluster_ctx.clb_nlist.net_name(inet).compare(name)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net name %s for net number %lu specified in the routing file does not match given %s", |
| name.c_str(), size_t(inet), cluster_ctx.clb_nlist.net_name(inet).c_str()); |
| } |
| |
| process_global_blocks(fp, inet, filename, lineno); |
| } else { |
| /* Not a global net */ |
| if (cluster_ctx.clb_nlist.net_is_ignored(inet)) { |
| VTR_LOG_WARN("Net %lu (%s) is marked as global in the netlist, but is non-global in the .route file\n", size_t(inet), cluster_ctx.clb_nlist.net_name(inet).c_str()); |
| } |
| |
| name = format_name(name); |
| |
| if (0 != cluster_ctx.clb_nlist.net_name(inet).compare(name)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net name %s for net number %lu specified in the routing file does not match given %s", |
| name.c_str(), size_t(inet), cluster_ctx.clb_nlist.net_name(inet).c_str()); |
| } |
| |
| process_nodes(fp, inet, filename, lineno); |
| } |
| input_tokens.clear(); |
| return; |
| } |
| |
| static void process_nodes(std::ifstream& fp, ClusterNetId inet, const char* filename, int& lineno) { |
| /* Not a global net. Goes through every node and add it into trace.head*/ |
| |
| auto& cluster_ctx = g_vpr_ctx.mutable_clustering(); |
| auto& device_ctx = g_vpr_ctx.mutable_device(); |
| auto& route_ctx = g_vpr_ctx.mutable_routing(); |
| auto& place_ctx = g_vpr_ctx.placement(); |
| |
| t_trace* tptr = route_ctx.trace[inet].head; |
| |
| /*remember the position of the last line in order to go back*/ |
| std::streampos oldpos = fp.tellg(); |
| int inode, x, y, x2, y2, ptc, switch_id, offset; |
| int node_count = 0; |
| std::string input; |
| std::vector<std::string> tokens; |
| |
| /*Walk through every line that begins with Node:*/ |
| while (std::getline(fp, input)) { |
| ++lineno; |
| |
| tokens.clear(); |
| tokens = vtr::split(input); |
| |
| if (tokens.empty()) { |
| continue; /*Skip blank lines*/ |
| } else if (tokens[0][0] == '#') { |
| continue; /*Skip commented lines*/ |
| } else if (tokens[0] == "Net") { |
| /*End of the nodes list, |
| * return by moving the position of next char of input stream to be before net*/ |
| fp.seekg(oldpos); |
| return; |
| } else if (input == "\n\nUsed in local cluster only, reserved one CLB pin\n\n") { |
| if (cluster_ctx.clb_nlist.net_sinks(inet).size() != 0) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net %d should be used in local cluster only, reserved one CLB pin"); |
| } |
| return; |
| } else if (tokens[0] == "Node:") { |
| /*An actual line, go through each node and add it to the route tree*/ |
| inode = atoi(tokens[1].c_str()); |
| auto& node = device_ctx.rr_nodes[inode]; |
| |
| /*First node needs to be source. It is isolated to correctly set heap head.*/ |
| if (node_count == 0 && tokens[2] != "SOURCE") { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "First node in routing has to be a source type"); |
| } |
| |
| /*Check node types if match rr graph*/ |
| if (tokens[2] != node.type_string()) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Node %d has a type that does not match the RR graph", inode); |
| } |
| |
| format_coordinates(x, y, tokens[3], inet, filename, lineno); |
| |
| if (tokens[4] == "to") { |
| format_coordinates(x2, y2, tokens[5], inet, filename, lineno); |
| if (node.xlow() != x || node.xhigh() != x2 || node.yhigh() != y2 || node.ylow() != y) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "The coordinates of node %d does not match the rr graph", inode); |
| } |
| offset = 2; |
| } else { |
| if (node.xlow() != x || node.xhigh() != x || node.yhigh() != y || node.ylow() != y) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "The coordinates of node %d does not match the rr graph", inode); |
| } |
| offset = 0; |
| } |
| |
| /* Verify types and ptc*/ |
| if (tokens[2] == "SOURCE" || tokens[2] == "SINK" || tokens[2] == "OPIN" || tokens[2] == "IPIN") { |
| if (tokens[4 + offset] == "Pad:" && !is_io_type(device_ctx.grid[x][y].type)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Node %d is of the wrong type", inode); |
| } |
| } else if (tokens[2] == "CHANX" || tokens[2] == "CHANY") { |
| if (tokens[4 + offset] != "Track:") { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "A %s node have to have track info", tokens[2].c_str()); |
| } |
| } |
| |
| ptc = atoi(tokens[5 + offset].c_str()); |
| if (node.ptc_num() != ptc) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "The ptc num of node %d does not match the rr graph", inode); |
| } |
| |
| /*Process switches and pb pin info if it is ipin or opin type*/ |
| if (tokens[6 + offset] != "Switch:") { |
| /*This is an opin or ipin, process its pin nums*/ |
| if (!is_io_type(device_ctx.grid[x][y].type) && (tokens[2] == "IPIN" || tokens[2] == "OPIN")) { |
| int pin_num = device_ctx.rr_nodes[inode].ptc_num(); |
| int height_offset = device_ctx.grid[x][y].height_offset; |
| ClusterBlockId iblock = place_ctx.grid_blocks[x][y - height_offset].blocks[0]; |
| t_pb_graph_pin* pb_pin = get_pb_graph_node_pin_from_block_pin(iblock, pin_num); |
| t_pb_type* pb_type = pb_pin->parent_node->pb_type; |
| |
| std::string pb_name, port_name; |
| int pb_pin_num; |
| |
| format_pin_info(pb_name, port_name, pb_pin_num, tokens[6 + offset]); |
| |
| if (pb_name != pb_type->name || port_name != pb_pin->port->name || pb_pin_num != pb_pin->pin_number) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "%d node does not have correct pins", inode); |
| } |
| } else { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "%d node does not have correct pins", inode); |
| } |
| switch_id = atoi(tokens[8 + offset].c_str()); |
| } else { |
| switch_id = atoi(tokens[7 + offset].c_str()); |
| } |
| |
| /* Allocate and load correct values to trace.head*/ |
| if (node_count == 0) { |
| route_ctx.trace[inet].head = alloc_trace_data(); |
| route_ctx.trace[inet].head->index = inode; |
| route_ctx.trace[inet].head->iswitch = switch_id; |
| route_ctx.trace[inet].head->next = nullptr; |
| tptr = route_ctx.trace[inet].head; |
| node_count++; |
| } else { |
| tptr->next = alloc_trace_data(); |
| tptr = tptr->next; |
| tptr->index = inode; |
| tptr->iswitch = switch_id; |
| tptr->next = nullptr; |
| node_count++; |
| } |
| } |
| /*stores last line so can easily go back to read*/ |
| oldpos = fp.tellg(); |
| } |
| } |
| |
| /*This function goes through all the blocks in a global net and verify it with the |
| * clustered netlist and the placement */ |
| static void process_global_blocks(std::ifstream& fp, ClusterNetId inet, const char* filename, int& lineno) { |
| auto& cluster_ctx = g_vpr_ctx.mutable_clustering(); |
| auto& place_ctx = g_vpr_ctx.placement(); |
| |
| std::string block, bnum_str; |
| int x, y; |
| std::vector<std::string> tokens; |
| int pin_counter = 0; |
| |
| std::streampos oldpos = fp.tellg(); |
| /*Walk through every block line*/ |
| while (std::getline(fp, block)) { |
| ++lineno; |
| tokens.clear(); |
| tokens = vtr::split(block); |
| |
| if (tokens.empty()) { |
| continue; /*Skip blank lines*/ |
| } else if (tokens[0][0] == '#') { |
| continue; /*Skip commented lines*/ |
| } else if (tokens[0] != "Block") { |
| /*End of blocks, go back to reading nets*/ |
| fp.seekg(oldpos); |
| return; |
| } else { |
| format_coordinates(x, y, tokens[4], inet, filename, lineno); |
| |
| /*remove ()*/ |
| bnum_str = format_name(tokens[2]); |
| /*remove #*/ |
| bnum_str.erase(bnum_str.begin()); |
| ClusterBlockId bnum(atoi(bnum_str.c_str())); |
| |
| /*Check for name, coordinate, and pins*/ |
| if (0 != cluster_ctx.clb_nlist.block_name(bnum).compare(tokens[1])) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Block %s for block number %lu specified in the routing file does not match given %s", |
| tokens[1].c_str(), size_t(bnum), cluster_ctx.clb_nlist.block_name(bnum).c_str()); |
| } |
| if (place_ctx.block_locs[bnum].loc.x != x || place_ctx.block_locs[bnum].loc.y != y) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "The placement coordinates (%d, %d) of %d block does not match given (%d, %d)", |
| x, y, place_ctx.block_locs[bnum].loc.x, place_ctx.block_locs[bnum].loc.y); |
| } |
| |
| int pin_index = cluster_ctx.clb_nlist.net_pin_physical_index(inet, pin_counter); |
| if (physical_tile_type(bnum)->pin_class[pin_index] != atoi(tokens[7].c_str())) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "The pin class %d of %lu net does not match given ", |
| atoi(tokens[7].c_str()), size_t(inet), physical_tile_type(bnum)->pin_class[pin_index]); |
| } |
| pin_counter++; |
| } |
| oldpos = fp.tellg(); |
| } |
| } |
| |
| static void format_coordinates(int& x, int& y, std::string coord, ClusterNetId net, const char* filename, const int lineno) { |
| /*Parse coordinates in the form of (x,y) into correct x and y values*/ |
| coord = format_name(coord); |
| std::stringstream coord_stream(coord); |
| if (!(coord_stream >> x)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net %lu has coordinates that is not in the form (x,y)", size_t(net)); |
| } |
| coord_stream.ignore(1, ' '); |
| if (!(coord_stream >> y)) { |
| vpr_throw(VPR_ERROR_ROUTE, filename, lineno, |
| "Net %lu has coordinates that is not in the form (x,y)", size_t(net)); |
| } |
| } |
| |
| static void format_pin_info(std::string& pb_name, std::string& port_name, int& pb_pin_num, std::string input) { |
| /*Parse the pin info in the form of pb_name.port_name[pb_pin_num] |
| *into its appropriate variables*/ |
| std::stringstream pb_info(input); |
| std::getline(pb_info, pb_name, '.'); |
| std::getline(pb_info, port_name, '['); |
| pb_info >> pb_pin_num; |
| if (!pb_info) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, |
| "Format of this pin info %s is incorrect", |
| input.c_str()); |
| } |
| } |
| |
| /*Return actual name by extracting it out of the form of (name)*/ |
| static std::string format_name(std::string name) { |
| if (name.length() > 2) { |
| name.erase(name.begin()); |
| name.erase(name.end() - 1); |
| return name; |
| } else { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, |
| "%s should be enclosed by parenthesis", |
| name.c_str()); |
| return nullptr; |
| } |
| } |