/******************************************************************** * This file includes functions that are used to annotate device-level * information, in particular the routing resource graph *******************************************************************/ /* Headers from vtrutil library */ #include "vtr_time.h" #include "vtr_assert.h" #include "vtr_log.h" /* Headers from openfpgautil library */ #include "openfpga_side_manager.h" /* Headers from vpr library */ #include "rr_graph_obj_util.h" #include "annotate_rr_graph.h" /* begin namespace openfpga */ namespace openfpga { constexpr char* VPR_DELAYLESS_SWITCH_NAME = "__vpr_delayless_switch__"; /* Build a RRChan Object with the given channel type and coorindators */ static RRChan build_one_rr_chan(const DeviceContext& vpr_device_ctx, const t_rr_type& chan_type, vtr::Point& chan_coord) { std::vector chan_rr_nodes; /* Create a rr_chan object and check if it is unique in the graph */ RRChan rr_chan; /* Fill the information */ rr_chan.set_type(chan_type); /* Collect rr_nodes for this channel */ chan_rr_nodes = find_rr_graph_chan_nodes(vpr_device_ctx.rr_graph, chan_coord.x(), chan_coord.y(), chan_type); /* Fill the rr_chan */ for (const RRNodeId& chan_rr_node : chan_rr_nodes) { rr_chan.add_node(vpr_device_ctx.rr_graph, chan_rr_node, vpr_device_ctx.rr_graph.node_segment(chan_rr_node)); } return rr_chan; } /* Build a General Switch Block (GSB) * which includes: * [I] A Switch Box subckt consists of following ports: * 1. Channel Y [x][y] inputs * 2. Channel X [x+1][y] inputs * 3. Channel Y [x][y-1] outputs * 4. Channel X [x][y] outputs * 5. Grid[x][y+1] Right side outputs pins * 6. Grid[x+1][y+1] Left side output pins * 7. Grid[x+1][y+1] Bottom side output pins * 8. Grid[x+1][y] Top side output pins * 9. Grid[x+1][y] Left side output pins * 10. Grid[x][y] Right side output pins * 11. Grid[x][y] Top side output pins * 12. Grid[x][y+1] Bottom side output pins * * -------------- -------------- * | | CBY | | * | Grid | ChanY | Grid | * | [x][y+1] | [x][y+1] | [x+1][y+1] | * | | | | * -------------- -------------- * ---------- * ChanX & CBX | Switch | ChanX * [x][y] | Box | [x+1][y] * | [x][y] | * ---------- * -------------- -------------- * | | | | * | Grid | ChanY | Grid | * | [x][y] | [x][y] | [x+1][y] | * | | | | * -------------- -------------- * For channels chanY with INC_DIRECTION on the top side, they should be marked as outputs * For channels chanY with DEC_DIRECTION on the top side, they should be marked as inputs * For channels chanY with INC_DIRECTION on the bottom side, they should be marked as inputs * For channels chanY with DEC_DIRECTION on the bottom side, they should be marked as outputs * For channels chanX with INC_DIRECTION on the left side, they should be marked as inputs * For channels chanX with DEC_DIRECTION on the left side, they should be marked as outputs * For channels chanX with INC_DIRECTION on the right side, they should be marked as outputs * For channels chanX with DEC_DIRECTION on the right side, they should be marked as inputs * * [II] A X-direction Connection Block [x][y] * The connection block shares the same routing channel[x][y] with the Switch Block * We just need to fill the ipin nodes at TOP and BOTTOM sides * as well as properly fill the ipin_grid_side information * [III] A Y-direction Connection Block [x][y+1] * The connection block shares the same routing channel[x][y+1] with the Switch Block * We just need to fill the ipin nodes at LEFT and RIGHT sides * as well as properly fill the ipin_grid_side information */ static RRGSB build_rr_gsb(const DeviceContext& vpr_device_ctx, const vtr::Point& gsb_range, const vtr::Point& gsb_coord) { /* Create an object to return */ RRGSB rr_gsb; VTR_ASSERT(gsb_coord.x() <= gsb_range.x()); VTR_ASSERT(gsb_coord.y() <= gsb_range.y()); /* Coordinator initialization */ rr_gsb.set_coordinate(gsb_coord.x(), gsb_coord.y()); /* Basic information*/ rr_gsb.init_num_sides(4); /* Fixed number of sides */ /* Find all rr_nodes of channels */ /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { /* Local variables inside this for loop */ SideManager side_manager(side); vtr::Point coordinate = rr_gsb.get_side_block_coordinate(side_manager.get_side()); RRChan rr_chan; std::vector> temp_opin_rr_nodes(2); enum e_side opin_grid_side[2] = {NUM_SIDES, NUM_SIDES}; enum PORTS chan_dir_to_port_dir_mapping[2] = {OUT_PORT, IN_PORT}; /* 0: INC_DIRECTION => ?; 1: DEC_DIRECTION => ? */ switch (side) { case TOP: /* TOP = 0 */ /* For the border, we should take special care */ if (gsb_coord.y() == gsb_range.y()) { rr_gsb.clear_one_side(side_manager.get_side()); break; } /* Routing channels*/ /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ /* Create a rr_chan object and check if it is unique in the graph */ rr_chan = build_one_rr_chan(vpr_device_ctx, CHANY, coordinate); chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ /* Build the Switch block: opin and opin_grid_side */ /* Assign grid side of OPIN */ /* Grid[x][y+1] RIGHT side outputs pins */ opin_grid_side[0] = RIGHT; /* Grid[x+1][y+1] left side outputs pins */ opin_grid_side[1] = LEFT; /* Include Grid[x][y+1] RIGHT side outputs pins */ temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x(), gsb_coord.y() + 1, OPIN, opin_grid_side[0]); /* Include Grid[x+1][y+1] Left side output pins */ temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x() + 1, gsb_coord.y() + 1, OPIN, opin_grid_side[1]); break; case RIGHT: /* RIGHT = 1 */ /* For the border, we should take special care */ if (gsb_coord.x() == gsb_range.x()) { rr_gsb.clear_one_side(side_manager.get_side()); break; } /* Routing channels*/ /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ /* Collect rr_nodes for Tracks for top: chany[x][y+1] */ /* Create a rr_chan object and check if it is unique in the graph */ rr_chan = build_one_rr_chan(vpr_device_ctx, CHANX, coordinate); chan_dir_to_port_dir_mapping[0] = OUT_PORT; /* INC_DIRECTION => OUT_PORT */ chan_dir_to_port_dir_mapping[1] = IN_PORT; /* DEC_DIRECTION => IN_PORT */ /* Build the Switch block: opin and opin_grid_side */ /* Assign grid side of OPIN */ /* Grid[x+1][y+1] BOTTOM side outputs pins */ opin_grid_side[0] = BOTTOM; /* Grid[x+1][y] TOP side outputs pins */ opin_grid_side[1] = TOP; /* include Grid[x+1][y+1] Bottom side output pins */ temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x() + 1, gsb_coord.y() + 1, OPIN, opin_grid_side[0]); /* include Grid[x+1][y] Top side output pins */ temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x() + 1, gsb_coord.y(), OPIN, opin_grid_side[1]); break; case BOTTOM: /* BOTTOM = 2*/ /* For the border, we should take special care */ if (gsb_coord.y() == 0) { rr_gsb.clear_one_side(side_manager.get_side()); break; } /* Routing channels*/ /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ /* Collect rr_nodes for Tracks for bottom: chany[x][y] */ /* Create a rr_chan object and check if it is unique in the graph */ rr_chan = build_one_rr_chan(vpr_device_ctx, CHANY, coordinate); chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ /* Build the Switch block: opin and opin_grid_side */ /* Assign grid side of OPIN */ /* Grid[x+1][y] LEFT side outputs pins */ opin_grid_side[0] = LEFT; /* Grid[x][y] RIGHT side outputs pins */ opin_grid_side[1] = RIGHT; /* include Grid[x+1][y] Left side output pins */ temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x() + 1, gsb_coord.y(), OPIN, opin_grid_side[0]); /* include Grid[x][y] Right side output pins */ temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x(), gsb_coord.y(), OPIN, opin_grid_side[1]); break; case LEFT: /* LEFT = 3 */ /* For the border, we should take special care */ if (gsb_coord.x() == 0) { rr_gsb.clear_one_side(side_manager.get_side()); break; } /* Routing channels*/ /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ /* Collect rr_nodes for Tracks for left: chanx[x][y] */ /* Create a rr_chan object and check if it is unique in the graph */ rr_chan = build_one_rr_chan(vpr_device_ctx, CHANX, coordinate); chan_dir_to_port_dir_mapping[0] = IN_PORT; /* INC_DIRECTION => IN_PORT */ chan_dir_to_port_dir_mapping[1] = OUT_PORT; /* DEC_DIRECTION => OUT_PORT */ /* Build the Switch block: opin and opin_grid_side */ /* Grid[x][y+1] BOTTOM side outputs pins */ opin_grid_side[0] = BOTTOM; /* Grid[x][y] TOP side outputs pins */ opin_grid_side[1] = TOP; /* include Grid[x][y+1] Bottom side outputs pins */ temp_opin_rr_nodes[0] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x(), gsb_coord.y() + 1, OPIN, opin_grid_side[0]); /* include Grid[x][y] Top side output pins */ temp_opin_rr_nodes[1] = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, gsb_coord.x(), gsb_coord.y(), OPIN, opin_grid_side[1]); break; default: VTR_LOG_ERROR("Invalid side index!\n"); exit(1); } /* Organize a vector of port direction */ if (0 < rr_chan.get_chan_width()) { std::vector rr_chan_dir; rr_chan_dir.resize(rr_chan.get_chan_width()); for (size_t itrack = 0; itrack < rr_chan.get_chan_width(); ++itrack) { /* Identify the directionality, record it in rr_node_direction */ if (INC_DIRECTION == vpr_device_ctx.rr_graph.node_direction(rr_chan.get_node(itrack))) { rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[0]; } else { VTR_ASSERT(DEC_DIRECTION == vpr_device_ctx.rr_graph.node_direction(rr_chan.get_node(itrack))); rr_chan_dir[itrack] = chan_dir_to_port_dir_mapping[1]; } } /* Fill chan_rr_nodes */ rr_gsb.add_chan_node(side_manager.get_side(), rr_chan, rr_chan_dir); } /* Fill opin_rr_nodes */ /* Copy from temp_opin_rr_node to opin_rr_node */ for (const RRNodeId& inode : temp_opin_rr_nodes[0]) { /* Grid[x+1][y+1] Bottom side outputs pins */ rr_gsb.add_opin_node(inode, side_manager.get_side()); } for (const RRNodeId& inode : temp_opin_rr_nodes[1]) { /* Grid[x+1][y] TOP side outputs pins */ rr_gsb.add_opin_node(inode, side_manager.get_side()); } /* Clean ipin_rr_nodes */ /* We do not have any IPIN for a Switch Block */ rr_gsb.clear_ipin_nodes(side_manager.get_side()); /* Clear the temp data */ temp_opin_rr_nodes[0].clear(); temp_opin_rr_nodes[1].clear(); opin_grid_side[0] = NUM_SIDES; opin_grid_side[1] = NUM_SIDES; } /* Side: TOP => 0, RIGHT => 1, BOTTOM => 2, LEFT => 3 */ for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { /* Local variables inside this for loop */ SideManager side_manager(side); size_t ix; size_t iy; enum e_side chan_side; std::vector temp_ipin_rr_nodes; enum e_side ipin_rr_node_grid_side; switch (side) { case TOP: /* TOP = 0 */ /* For the bording, we should take special care */ /* Check if left side chan width is 0 or not */ chan_side = LEFT; /* Build the connection block: ipin and ipin_grid_side */ /* BOTTOM side INPUT Pins of Grid[x][y+1] */ ix = rr_gsb.get_sb_x(); iy = rr_gsb.get_sb_y() + 1; ipin_rr_node_grid_side = BOTTOM; break; case RIGHT: /* RIGHT = 1 */ /* For the bording, we should take special care */ /* Check if TOP side chan width is 0 or not */ chan_side = TOP; /* Build the connection block: ipin and ipin_grid_side */ /* LEFT side INPUT Pins of Grid[x+1][y+1] */ ix = rr_gsb.get_sb_x() + 1; iy = rr_gsb.get_sb_y() + 1; ipin_rr_node_grid_side = LEFT; break; case BOTTOM: /* BOTTOM = 2*/ /* For the bording, we should take special care */ /* Check if left side chan width is 0 or not */ chan_side = LEFT; /* Build the connection block: ipin and ipin_grid_side */ /* TOP side INPUT Pins of Grid[x][y] */ ix = rr_gsb.get_sb_x(); iy = rr_gsb.get_sb_y(); ipin_rr_node_grid_side = TOP; break; case LEFT: /* LEFT = 3 */ /* For the bording, we should take special care */ /* Check if left side chan width is 0 or not */ chan_side = TOP; /* Build the connection block: ipin and ipin_grid_side */ /* RIGHT side INPUT Pins of Grid[x][y+1] */ ix = rr_gsb.get_sb_x(); iy = rr_gsb.get_sb_y() + 1; ipin_rr_node_grid_side = RIGHT; break; default: VTR_LOG_ERROR("Invalid side index!\n"); exit(1); } /* If there is no channel at this side, we skip ipin_node annotation */ if (0 == rr_gsb.get_chan_width(chan_side)) { continue; } /* Collect IPIN rr_nodes*/ temp_ipin_rr_nodes = find_rr_graph_grid_nodes(vpr_device_ctx.rr_graph, vpr_device_ctx.grid, ix, iy, IPIN, ipin_rr_node_grid_side); /* Fill the ipin nodes of RRGSB */ for (const RRNodeId& inode : temp_ipin_rr_nodes) { rr_gsb.add_ipin_node(inode, side_manager.get_side()); } /* Clear the temp data */ temp_ipin_rr_nodes.clear(); } return rr_gsb; } /******************************************************************** * Build the annotation for the routing resource graph * by collecting the nodes to the General Switch Block context *******************************************************************/ void annotate_device_rr_gsb(const DeviceContext& vpr_device_ctx, DeviceRRGSB& device_rr_gsb, const bool& verbose_output) { vtr::ScopedStartFinishTimer timer("Build General Switch Block(GSB) annotation on top of routing resource graph"); /* Note that the GSB array is smaller than the grids by 1 column and 1 row!!! */ vtr::Point gsb_range(vpr_device_ctx.grid.width() - 1, vpr_device_ctx.grid.height() - 1); device_rr_gsb.reserve(gsb_range); VTR_LOGV(verbose_output, "Start annotation GSB up to [%lu][%lu]\n", gsb_range.x(), gsb_range.y()); size_t gsb_cnt = 0; /* For each switch block, determine the size of array */ for (size_t ix = 0; ix < gsb_range.x(); ++ix) { for (size_t iy = 0; iy < gsb_range.y(); ++iy) { /* Here we give the builder the fringe coordinates so that it can handle the GSBs at the borderside correctly */ const RRGSB& rr_gsb = build_rr_gsb(vpr_device_ctx, vtr::Point(vpr_device_ctx.grid.width() - 2, vpr_device_ctx.grid.height() - 2), vtr::Point(ix, iy)); /* TODO: sort drive_rr_nodes should be done when building the tileable rr_graph */ //sort_rr_gsb_drive_rr_nodes(rr_gsb); /* Add to device_rr_gsb */ vtr::Point gsb_coordinate = rr_gsb.get_sb_coordinate(); device_rr_gsb.add_rr_gsb(gsb_coordinate, rr_gsb); gsb_cnt++; /* Update counter */ /* Print info */ VTR_LOG("[%lu%] Backannotated GSB[%lu][%lu]\r", 100 * gsb_cnt / (gsb_range.x() * gsb_range.y()), ix, iy); } } /* Report number of unique mirrors */ VTR_LOG("Backannotated %d General Switch Blocks (GSBs).\n", gsb_range.x() * gsb_range.y()); } /******************************************************************** * Build the link between rr_graph switches to their physical circuit models * The binding is done based on the name of rr_switches defined in the * OpenFPGA arch XML *******************************************************************/ static void annotate_rr_switch_circuit_models(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { size_t count = 0; for (size_t iswitch = 0; iswitch < vpr_device_ctx.rr_switch_inf.size(); iswitch++) { std::string switch_name(vpr_device_ctx.rr_switch_inf[iswitch].name); /* Skip the delayless switch, which is only used by the edges between * - SOURCE and OPIN * - IPIN and SINK */ if (switch_name == std::string(VPR_DELAYLESS_SWITCH_NAME)) { continue; } CircuitModelId circuit_model = CircuitModelId::INVALID(); /* The name-to-circuit mapping is stored in either cb_switch-to-circuit or sb_switch-to-circuit, * Try to find one and update the device annotation */ if (0 < openfpga_arch.cb_switch2circuit.count(switch_name)) { circuit_model = openfpga_arch.cb_switch2circuit.at(switch_name); } if (0 < openfpga_arch.sb_switch2circuit.count(switch_name)) { if (CircuitModelId::INVALID() != circuit_model) { VTR_LOG_WARN("Found a connection block and a switch block switch share the same name '%s' and binded to different circuit models '%s' and '%s'!\nWill use the switch block switch binding!\n", switch_name.c_str(), openfpga_arch.circuit_lib.model_name(circuit_model).c_str(), openfpga_arch.circuit_lib.model_name(openfpga_arch.sb_switch2circuit.at(switch_name)).c_str()); } circuit_model = openfpga_arch.sb_switch2circuit.at(switch_name); } /* Cannot find a circuit model, error out! */ if (CircuitModelId::INVALID() == circuit_model) { VTR_LOG_ERROR("Fail to find a circuit model for a routing resource graph switch '%s'!\nPlease check your OpenFPGA architecture XML!\n", switch_name.c_str()); exit(1); } /* Now update the device annotation */ vpr_device_annotation.add_rr_switch_circuit_model(RRSwitchId(iswitch), circuit_model); VTR_LOGV(verbose_output, "Binded a routing resource graph switch '%s' to circuit model '%s'\n", switch_name.c_str(), openfpga_arch.circuit_lib.model_name(circuit_model).c_str()); count++; } VTR_LOG("Binded %lu routing resource graph switches to circuit models\n", count); } /******************************************************************** * Build the link between rr_graph switches and segments to their * physical circuit models *******************************************************************/ void annotate_rr_graph_circuit_models(const DeviceContext& vpr_device_ctx, const Arch& openfpga_arch, VprDeviceAnnotation& vpr_device_annotation, const bool& verbose_output) { /* Iterate over each rr_switch in the device context and bind with names */ annotate_rr_switch_circuit_models(vpr_device_ctx, openfpga_arch, vpr_device_annotation, verbose_output); } } /* end namespace openfpga */