blob: 6bdfeb2b1b304ed4869ddf7aac20e09de039830e [file] [log] [blame]
/* 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;
}
}