| /*********************************** Top-level Summary ************************************* |
| * This is VPR's main graphics application program. The program interacts with graphics.c, |
| * which provides an API for displaying graphics on both X11 and Win32. The most important |
| * subroutine in this file is drawscreen(), which is a callback function that X11 or Win32 |
| * will call whenever the screen needs to be updated. Then, drawscreen() will decide what |
| * drawing subroutines to call depending on whether PLACEMENT or ROUTING is shown on screen |
| * and whether any of the menu buttons has been triggered. As a note, looks into draw_global.c |
| * for understanding the data structures associated with drawing. |
| * |
| * Authors: Vaughn Betz, Long Yu (Mike) Wang |
| * Last updated: August 2013 |
| */ |
| |
| #include <cstdio> |
| #include <cfloat> |
| #include <cstring> |
| #include <cmath> |
| #include <algorithm> |
| #include <sstream> |
| #include <array> |
| using namespace std; |
| |
| #include "vtr_assert.h" |
| #include "vtr_ndoffsetmatrix.h" |
| #include "vtr_memory.h" |
| #include "vtr_log.h" |
| #include "vtr_color_map.h" |
| |
| #include "vpr_utils.h" |
| #include "vpr_error.h" |
| |
| #include "globals.h" |
| #include "graphics.h" |
| #include "draw.h" |
| #include "read_xml_arch_file.h" |
| #include "draw_global.h" |
| #include "intra_logic_block.h" |
| #include "atom_netlist.h" |
| #include "tatum/report/TimingPathCollector.hpp" |
| #include "hsl.h" |
| #include "route_export.h" |
| |
| #ifdef WIN32 /* For runtime tracking in WIN32. The clock() function defined in time.h will * |
| * track CPU runtime. */ |
| # include <time.h> |
| #else /* For X11. The clock() function in time.h will not output correct time difference * |
| * for X11, because the graphics is processed by the Xserver rather than local CPU, * |
| * which means tracking CPU time will not be the same as the actual wall clock time. * |
| * Thus, so use gettimeofday() in sys/time.h to track actual calendar time. */ |
| # include <sys/time.h> |
| #endif |
| |
| //To process key presses we need the X11 keysym definitions, |
| //which are unavailable when building with MINGW |
| #if defined(X11) && !defined(__MINGW32__) |
| # include <X11/keysym.h> |
| #endif |
| |
| #include "rr_graph.h" |
| #include "route_util.h" |
| #include "place_macro.h" |
| |
| /****************************** Define Macros *******************************/ |
| |
| #define DEFAULT_RR_NODE_COLOR BLACK |
| //#define TIME_DRAWSCREEN /* Enable if want to track runtime for drawscreen() */ |
| |
| //The arrow head position for turning/straight-thru connections in a switch box |
| constexpr float SB_EDGE_TURN_ARROW_POSITION = 0.2; |
| constexpr float SB_EDGE_STRAIGHT_ARROW_POSITION = 0.95; |
| |
| constexpr float EMPTY_BLOCK_LIGHTEN_FACTOR = 0.10; |
| |
| //Kelly's maximum contrast colors are selected to be easily distinguishable as described in: |
| // Kenneth Kelly, "Twenty-Two Colors of Maximum Contrast", Color Eng. 3(6), 1943 |
| //We use these to highlight a relatively small number of things (e.g. stages in a critical path, |
| //a subset of selected net) where it is important for them to be visually distinct. |
| const std::vector<t_color> kelly_max_contrast_colors = { |
| //t_color(242, 243, 244), //white: skip white since it doesn't contrast well with VPR's light background |
| t_color(34, 34, 34), //black |
| t_color(243, 195, 0), //yellow |
| t_color(135, 86, 146), //purple |
| t_color(243, 132, 0), //orange |
| t_color(161, 202, 241), //light blue |
| t_color(190, 0, 50), //red |
| t_color(194, 178, 128), //buf |
| t_color(132, 132, 130), //gray |
| t_color(0, 136, 86), //green |
| t_color(230, 143, 172), //purplish pink |
| t_color(0, 103, 165), //blue |
| t_color(249, 147, 121), //yellowish pink |
| t_color(96, 78, 151), //violet |
| t_color(246, 166, 0), //orange yellow |
| t_color(179, 68, 108), //purplish red |
| t_color(220, 211, 0), //greenish yellow |
| t_color(136, 45, 23), //redish brown |
| t_color(141, 182, 0), //yellow green |
| t_color(101, 69, 34), //yellowish brown |
| t_color(226, 88, 34), //reddish orange |
| t_color(43, 61, 38) //olive green |
| }; |
| |
| //The colours used to draw block types |
| const std::vector<color_types> block_type_colors = { |
| //This first set of colours is somewhat curated to yield |
| //a nice colour pallette |
| BISQUE, //EMPTY type is usually the type with index 0, so this colour |
| //usually unused |
| LIGHTGREY, |
| LIGHTSKYBLUE, |
| THISTLE, |
| KHAKI, |
| CORAL, |
| TURQUOISE, |
| MEDIUMPURPLE, |
| DARKSLATEBLUE, |
| DARKKHAKI, |
| LIGHTMEDIUMBLUE, |
| SADDLEBROWN, |
| FIREBRICK, |
| LIMEGREEN, |
| PLUM, |
| |
| //However, if we have lots of block types we just assign arbitrary HTML |
| //colours. Note that these are shuffled (instead of in alphabetical order) |
| //since some colours which are alphabetically close are also close in |
| //shade (making them difficult to distinguish) |
| DARKGREEN, |
| PALEVIOLETRED, |
| BLUE, |
| FORESTGREEN, |
| WHEAT, |
| GOLD, |
| MOCCASIN, |
| MEDIUMORCHID, |
| SKYBLUE, |
| WHITESMOKE, |
| LIME, |
| MEDIUMSLATEBLUE, |
| TOMATO, |
| CYAN, |
| OLIVE, |
| LIGHTGRAY, |
| STEELBLUE, |
| LIGHTCORAL, |
| IVORY, |
| MEDIUMVIOLETRED, |
| SNOW, |
| DARKGRAY, |
| GREY, |
| GRAY, |
| YELLOW, |
| REBECCAPURPLE, |
| DARKCYAN, |
| MIDNIGHTBLUE, |
| ROSYBROWN, |
| CORNSILK, |
| NAVAJOWHITE, |
| BLANCHEDALMOND, |
| ORCHID, |
| LIGHTGOLDENRODYELLOW, |
| MAROON, |
| GREENYELLOW, |
| SILVER, |
| PALEGOLDENROD, |
| LAWNGREEN, |
| DIMGREY, |
| DARKVIOLET, |
| DARKTURQUOISE, |
| INDIGO, |
| DARKORANGE, |
| PAPAYAWHIP, |
| MINTCREAM, |
| GREEN, |
| DARKMAGENTA, |
| MAGENTA, |
| LIGHTSLATEGRAY, |
| CHARTREUSE, |
| GHOSTWHITE, |
| LIGHTCYAN, |
| SIENNA, |
| GOLDENROD, |
| DARKSLATEGRAY, |
| OLDLACE, |
| SEASHELL, |
| SPRINGGREEN, |
| MEDIUMTURQUOISE, |
| LEMONCHIFFON, |
| MISTYROSE, |
| OLIVEDRAB, |
| LIGHTBLUE, |
| CHOCOLATE, |
| SEAGREEN, |
| DEEPPINK, |
| LIGHTSEAGREEN, |
| FLORALWHITE, |
| CADETBLUE, |
| AZURE, |
| BURLYWOOD, |
| AQUAMARINE, |
| BROWN, |
| POWDERBLUE, |
| HOTPINK, |
| MEDIUMBLUE, |
| BLUEVIOLET, |
| GREY75, |
| PURPLE, |
| TEAL, |
| ANTIQUEWHITE, |
| DEEPSKYBLUE, |
| SLATEGRAY, |
| SALMON, |
| SLATEBLUE, |
| DARKORCHID, |
| LIGHTPINK, |
| DARKBLUE, |
| PEACHPUFF, |
| PALEGREEN, |
| DARKSALMON, |
| DARKOLIVEGREEN, |
| DARKSEAGREEN, |
| VIOLET, |
| RED, |
| DARKSLATEGREY, |
| PALETURQUOISE, |
| DARKRED, |
| SLATEGREY, |
| HONEYDEW, |
| AQUA, |
| LIGHTSTEELBLUE, |
| DODGERBLUE, |
| MEDIUMSPRINGGREEN, |
| NAVY, |
| GAINSBORO, |
| LIGHTYELLOW, |
| CRIMSON, |
| FUCHSIA, |
| DARKGOLDENROD, |
| SANDYBROWN, |
| BEIGE, |
| LINEN, |
| ORANGERED, |
| ROYALBLUE, |
| LAVENDER, |
| TAN, |
| YELLOWGREEN, |
| CORNFLOWERBLUE, |
| LAVENDERBLUSH, |
| MEDIUMSEAGREEN, |
| PINK, |
| GREY55, |
| PERU, |
| LIGHTGREEN, |
| LIGHTSALMON, |
| INDIANRED, |
| DIMGRAY, |
| LIGHTSLATEGREY, |
| MEDIUMAQUAMARINE, |
| DARKGREY, |
| ORANGE, |
| ALICEBLUE, |
| }; |
| |
| //FIXME: ugly hack |
| extern t_pl_macro* pl_macros; |
| extern int num_pl_macros; |
| |
| /************************** File Scope Variables ****************************/ |
| |
| std::string rr_highlight_message; |
| |
| /********************** Subroutines local to this module ********************/ |
| |
| static void toggle_nets(void (*drawscreen)()); |
| static void toggle_rr(void (*drawscreen)()); |
| static void toggle_congestion(void (*drawscreen)()); |
| static void toggle_routing_congestion_cost(void (*drawscreen)()); |
| static void toggle_routing_bounding_box(void (*drawscreen_ptr)()); |
| static void toggle_routing_util(void (*drawscreen_ptr)()); |
| static void toggle_crit_path(void (*drawscreen_ptr)()); |
| static void toggle_block_pin_util(void (*drawscreen_ptr)()); |
| static void toggle_router_rr_costs(void (*drawscreen_ptr)()); |
| static void toggle_placement_macros(void (*drawscreen_ptr)()); |
| |
| static void drawscreen(); |
| static void redraw_screen(); |
| static void drawplace(); |
| static void drawnets(); |
| static void drawroute(enum e_draw_net_type draw_net_type); |
| static void draw_congestion(); |
| static void draw_routing_costs(); |
| static void draw_routing_bb(); |
| static void draw_routing_util(); |
| static void draw_crit_path(); |
| static void draw_placement_macros(); |
| |
| static void highlight_blocks(float x, float y, t_event_buttonPressed button_info); |
| static void act_on_mouse_over(float x, float y); |
| static void deselect_all(); |
| |
| static void act_on_key_press(char key_pressed, int keysym); |
| |
| static void draw_routed_net(ClusterNetId net); |
| void draw_partial_route(const std::vector<int>& rr_nodes_to_draw); |
| static void draw_rr(); |
| static void draw_rr_edges(int from_node); |
| static void draw_rr_pin(int inode, const t_color& color); |
| static void draw_rr_chan(int inode, const t_color color); |
| static void draw_rr_src_sink(int inode, t_color color); |
| static t_bound_box draw_get_rr_chan_bbox(int inode); |
| static void draw_pin_to_chan_edge(int pin_node, int chan_node); |
| static void draw_x(float x, float y, float size); |
| static void draw_pin_to_pin(int opin, int ipin); |
| static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, bool buffered, bool switch_configurable); |
| static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type); |
| static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type); |
| static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type); |
| static int get_track_num(int inode, const vtr::OffsetMatrix<int>& chanx_track, const vtr::OffsetMatrix<int>& chany_track); |
| static bool draw_if_net_highlighted(ClusterNetId inet); |
| static void draw_highlight_fan_in_fan_out(const std::set<int>& nodes); |
| static void highlight_nets(char* message, int hit_node); |
| static int draw_check_rr_node_hit(float click_x, float click_y); |
| static std::set<int> draw_expand_non_configurable_rr_nodes(int hit_node); |
| static void draw_expand_non_configurable_rr_nodes_recurr(int from_node, std::set<int>& expanded_nodes); |
| static bool highlight_rr_nodes(float x, float y); |
| static void draw_highlight_blocks_color(t_type_ptr type, ClusterBlockId blk_id); |
| static void draw_reset_blk_colors(); |
| static void draw_reset_blk_color(ClusterBlockId blk_id); |
| |
| static inline bool LOD_screen_area_test_square(float width, float screen_area_threshold); |
| static inline bool default_triangle_LOD_screen_area_test(); |
| static inline bool triangle_LOD_screen_area_test(float arrow_size); |
| |
| static inline void draw_mux_with_size(t_point origin, e_side orientation, float height, int size); |
| static inline t_bound_box draw_mux(t_point origin, e_side orientation, float height); |
| static inline t_bound_box draw_mux(t_point origin, e_side orientation, float height, float width, float height_scale); |
| |
| static void draw_flyline_timing_edge(t_point start, t_point end, float incr_delay); |
| static void draw_routed_timing_edge(tatum::NodeId start_tnode, tatum::NodeId end_tnode, float incr_delay, t_color color); |
| static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::NodeId sink_tnode, t_color color); |
| static std::vector<int> trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin); |
| static bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, int sink_rr_node, std::vector<int>& rr_nodes_on_path); |
| static int find_edge(int prev_inode, int inode); |
| |
| t_color to_t_color(vtr::Color<float> color); |
| static void draw_color_map_legend(const vtr::ColorMap& cmap); |
| |
| t_color get_block_type_color(t_type_ptr type); |
| t_color lighten_color(t_color color, float amount); |
| |
| static void draw_block_pin_util(); |
| |
| static float get_router_rr_cost(const t_rr_node_route_inf node_inf, e_draw_router_rr_cost draw_router_rr_cost); |
| static void draw_router_rr_costs(); |
| |
| static void draw_rr_costs(const std::vector<float>& rr_costs, bool lowest_cost_first = true); |
| |
| /********************** Subroutine definitions ******************************/ |
| |
| void init_graphics_state(bool show_graphics_val, int gr_automode_val, enum e_route_type route_type) { |
| /* Call accessor functions to retrieve global variables. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| /* Sets the static show_graphics and gr_automode variables to the * |
| * desired values. They control if graphics are enabled and, if so, * |
| * how often the user is prompted for input. */ |
| |
| draw_state->show_graphics = show_graphics_val; |
| draw_state->gr_automode = gr_automode_val; |
| draw_state->draw_route_type = route_type; |
| } |
| |
| void update_screen(ScreenUpdatePriority priority, const char* msg, enum pic_type pic_on_screen_val, std::shared_ptr<SetupTimingInfo> setup_timing_info) { |
| /* Updates the screen if the user has requested graphics. The priority * |
| * value controls whether or not the Proceed button must be clicked to * |
| * continue. Saves the pic_on_screen_val to allow pan and zoom redraws. */ |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (!draw_state->show_graphics) /* Graphics turned off */ |
| return; |
| |
| /* If it's the type of picture displayed has changed, set up the proper * |
| * buttons. */ |
| if (draw_state->pic_on_screen != pic_on_screen_val) { //State changed |
| if (pic_on_screen_val == PLACEMENT && draw_state->pic_on_screen == NO_PICTURE) { |
| //Placement first to open |
| create_button("Window", "Toggle Nets", toggle_nets); |
| create_button("Toggle Nets", "Blk Internal", toggle_blk_internal); |
| create_button("Blk Internal", "Blk Pin Util", toggle_block_pin_util); |
| create_button("Blk Pin Util", "Place Macros", toggle_placement_macros); |
| if (setup_timing_info) { |
| create_button("Place Macros", "Crit. Path", toggle_crit_path); |
| } |
| } else if (pic_on_screen_val == ROUTING && draw_state->pic_on_screen == PLACEMENT) { |
| //Routing, opening after placement |
| create_button("Blk Internal", "Toggle RR", toggle_rr); |
| create_button("Toggle RR", "Congestion", toggle_congestion); |
| create_button("Congestion", "Cong. Cost", toggle_routing_congestion_cost); |
| create_button("Cong. Cost", "Route BB", toggle_routing_bounding_box); |
| create_button("Route BB", "Routing Util", toggle_routing_util); |
| create_button("Routing Util", "Router Cost", toggle_router_rr_costs); |
| } else if (pic_on_screen_val == PLACEMENT && draw_state->pic_on_screen == ROUTING) { |
| //Placement, opening after routing |
| |
| //Clean-up routing specific-buttons |
| destroy_button("Toggle RR"); |
| destroy_button("Congestion"); |
| destroy_button("Cong. Cost"); |
| destroy_button("Route BB"); |
| destroy_button("Route Util"); |
| destroy_button("Router Cost"); |
| } else if (pic_on_screen_val == ROUTING |
| && draw_state->pic_on_screen == NO_PICTURE) { |
| //Routing opening first |
| create_button("Window", "Toggle Nets", toggle_nets); |
| create_button("Toggle Nets", "Blk Internal", toggle_blk_internal); |
| create_button("Blk Internal", "Blk Pin Util", toggle_block_pin_util); |
| create_button("Blk Pin Util", "Place Macros", toggle_placement_macros); |
| create_button("Place Macros", "Toggle RR", toggle_rr); |
| create_button("Toggle RR", "Congestion", toggle_congestion); |
| create_button("Congestion", "Cong. Cost", toggle_routing_congestion_cost); |
| create_button("Cong. Cost", "Route BB", toggle_routing_bounding_box); |
| create_button("Route BB", "Routing Util", toggle_routing_util); |
| create_button("Routing Util", "Router Cost", toggle_router_rr_costs); |
| if (setup_timing_info) { |
| create_button("Cong. Cost", "Crit. Path", toggle_crit_path); |
| } |
| } |
| } |
| /* Save the main message. */ |
| |
| vtr::strncpy(draw_state->default_message, msg, vtr::bufsize); |
| |
| draw_state->setup_timing_info = setup_timing_info; |
| |
| draw_state->pic_on_screen = pic_on_screen_val; |
| update_message(msg); |
| drawscreen(); |
| |
| //Has the user asked us to pause at the next screen updated? |
| bool forced_pause = g_vpr_ctx.forced_pause(); |
| |
| if (int(priority) >= draw_state->gr_automode || forced_pause) { |
| if (forced_pause) { |
| VTR_LOG("Starting interactive graphics (due to user interrupt)\n"); |
| g_vpr_ctx.set_forced_pause(false); //Reset pause flag |
| } |
| set_mouse_move_input(true); //Enable act_on_mouse_over callback |
| set_keypress_input(true); //Enable act_on_mouse_over callback |
| event_loop(highlight_blocks, act_on_mouse_over, act_on_key_press, drawscreen); |
| } else { |
| flushinput(); |
| } |
| } |
| |
| static void drawscreen() { |
| #ifdef TIME_DRAWSCREEN |
| /* This can be used to test how long it takes for the redrawing routing to finish * |
| * updating the screen for a given input which would cause the screen to be redrawn.*/ |
| |
| # ifdef WIN32 |
| clock_t drawscreen_begin, drawscreen_end; |
| drawscreen_begin = clock(); |
| |
| # else /* For X11. The clock() function in time.h does not output correct time difference * |
| * in Linux, so use gettimeofday() in sys/time.h for accurate runtime tracking. */ |
| struct timeval begin; |
| gettimeofday(&begin, NULL); /* get start time */ |
| |
| unsigned long begin_time; |
| begin_time = begin.tv_sec * 1000000 + begin.tv_usec; |
| # endif |
| #endif |
| |
| /* This is the screen redrawing routine that event_loop assumes exists. * |
| * It erases whatever is on screen, then calls redraw_screen to redraw * |
| * it. */ |
| |
| set_drawing_buffer(OFF_SCREEN); |
| |
| clearscreen(); |
| redraw_screen(); |
| |
| copy_off_screen_buffer_to_screen(); |
| |
| #ifdef TIME_DRAWSCREEN |
| |
| # ifdef WIN32 |
| drawscreen_end = clock(); |
| |
| VTR_LOG("Drawscreen took %f seconds.\n", (float)(drawscreen_end - drawscreen_begin) / CLOCKS_PER_SEC); |
| |
| # else /* X11 */ |
| struct timeval end; |
| gettimeofday(&end, NULL); /* get end time */ |
| |
| unsigned long end_time; |
| end_time = end.tv_sec * 1000000 + end.tv_usec; |
| |
| unsigned long time_diff_microsec; |
| time_diff_microsec = end_time - begin_time; |
| |
| VTR_LOG("Drawscreen took %ld microseconds\n", time_diff_microsec); |
| # endif /* WIN32 */ |
| #endif /* TIME_DRAWSCREEN */ |
| } |
| |
| static void redraw_screen() { |
| /* The screen redrawing routine called by drawscreen and * |
| * highlight_blocks. Call this routine instead of drawscreen if * |
| * you know you don't need to erase the current graphics, and want * |
| * to avoid a screen "flash". */ |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| setfontsize(14); |
| |
| draw_block_pin_util(); |
| |
| drawplace(); |
| |
| draw_internal_draw_subblk(); |
| |
| if (draw_state->pic_on_screen == PLACEMENT) { |
| switch (draw_state->show_nets) { |
| case DRAW_NETS: |
| drawnets(); |
| break; |
| case DRAW_LOGICAL_CONNECTIONS: |
| break; |
| default: |
| break; |
| } |
| |
| } else { /* ROUTING on screen */ |
| |
| switch (draw_state->show_nets) { |
| case DRAW_NETS: |
| drawroute(ALL_NETS); |
| break; |
| case DRAW_LOGICAL_CONNECTIONS: |
| // fall through |
| default: |
| draw_rr(); |
| break; |
| } |
| |
| draw_congestion(); |
| |
| draw_routing_costs(); |
| |
| draw_router_rr_costs(); |
| |
| draw_routing_util(); |
| |
| draw_routing_bb(); |
| } |
| |
| draw_placement_macros(); |
| |
| draw_crit_path(); |
| |
| draw_logical_connections(); |
| |
| if (draw_state->color_map) { |
| draw_color_map_legend(*draw_state->color_map); |
| draw_state->color_map.reset(); //Free color map in preparation for next redraw |
| } |
| } |
| |
| static void toggle_nets(void (*drawscreen_ptr)()) { |
| /* Enables/disables drawing of nets when a the user clicks on a button. * |
| * Also disables drawing of routing resources. See graphics.c for details * |
| * of how buttons work. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| enum e_draw_nets new_state; |
| |
| switch (draw_state->show_nets) { |
| case DRAW_NO_NETS: |
| new_state = DRAW_NETS; |
| break; |
| case DRAW_NETS: |
| new_state = DRAW_LOGICAL_CONNECTIONS; |
| break; |
| default: |
| case DRAW_LOGICAL_CONNECTIONS: |
| new_state = DRAW_NO_NETS; |
| break; |
| } |
| |
| draw_state->reset_nets_congestion_and_rr(); |
| draw_state->show_nets = new_state; |
| |
| update_message(draw_state->default_message); |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_rr(void (*drawscreen_ptr)()) { |
| /* Cycles through the options for viewing the routing resources available * |
| * in an FPGA. If a routing isn't on screen, the routing graph hasn't been * |
| * built, and this routine doesn't switch the view. Otherwise, this routine * |
| * switches to the routing resource view. Clicking on the toggle cycles * |
| * through the options: DRAW_NO_RR, DRAW_ALL_RR, DRAW_ALL_BUT_BUFFERS_RR, * |
| * DRAW_NODES_AND_SBOX_RR, and DRAW_NODES_RR. */ |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| enum e_draw_rr_toggle new_state = (enum e_draw_rr_toggle)(((int)draw_state->draw_rr_toggle + 1) |
| % ((int)DRAW_RR_TOGGLE_MAX)); |
| draw_state->reset_nets_congestion_and_rr(); |
| draw_state->draw_rr_toggle = new_state; |
| |
| update_message(draw_state->default_message); |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_congestion(void (*drawscreen_ptr)()) { |
| /* Turns the congestion display on and off. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| e_draw_congestion new_state = (enum e_draw_congestion)(((int)draw_state->show_congestion + 1) |
| % ((int)DRAW_CONGEST_MAX)); |
| draw_state->reset_nets_congestion_and_rr(); |
| draw_state->show_congestion = new_state; |
| |
| if (draw_state->show_congestion == DRAW_NO_CONGEST) { |
| update_message(draw_state->default_message); |
| } |
| |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_routing_congestion_cost(void (*drawscreen_ptr)()) { |
| //Turns routing congestion costs on and off |
| t_draw_state* draw_state = get_draw_state_vars(); |
| e_draw_routing_costs new_state = (enum e_draw_routing_costs)(((int)draw_state->show_routing_costs + 1) |
| % ((int)DRAW_ROUTING_COST_MAX)); |
| |
| draw_state->reset_nets_congestion_and_rr(); |
| draw_state->show_routing_costs = new_state; |
| |
| if (draw_state->show_routing_costs == DRAW_NO_ROUTING_COSTS) { |
| update_message(draw_state->default_message); |
| } |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_routing_bounding_box(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| if (route_ctx.route_bb.size() == 0) { |
| return; //Nothing to draw |
| } else if ((size_t)draw_state->show_routing_bb == route_ctx.route_bb.size()) { |
| draw_state->show_routing_bb = OPEN; //Done going through all BB |
| } else if (draw_state->show_routing_bb == OPEN) { |
| draw_state->show_routing_bb = 0; //Start at first BB |
| } else { |
| ++draw_state->show_routing_bb; //Next net BB |
| } |
| |
| if (draw_state->show_routing_bb == OPEN) { |
| update_message(draw_state->default_message); |
| } |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_routing_util(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| e_draw_routing_util new_state = (enum e_draw_routing_util)(((int)draw_state->show_routing_util + 1) |
| % ((int)DRAW_ROUTING_UTIL_MAX)); |
| draw_state->show_routing_util = new_state; |
| |
| if (draw_state->show_routing_util == DRAW_NO_ROUTING_UTIL) { |
| update_message(draw_state->default_message); |
| } |
| drawscreen_ptr(); |
| } |
| |
| void toggle_blk_internal(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state; |
| |
| /* Call accessor function to retrieve global variables. */ |
| draw_state = get_draw_state_vars(); |
| |
| /* Increment the depth of sub-blocks to be shown */ |
| draw_state->show_blk_internal++; |
| /* If depth exceeds maximum sub-block level in pb_graph, then |
| * disable internals drawing |
| */ |
| if (draw_state->show_blk_internal > draw_state->max_sub_blk_lvl) |
| draw_state->show_blk_internal = 0; |
| |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_block_pin_util(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| e_draw_block_pin_util new_state = (enum e_draw_block_pin_util)(((int)draw_state->show_blk_pin_util + 1) |
| % ((int)DRAW_PIN_UTIL_MAX)); |
| |
| draw_state->show_blk_pin_util = new_state; |
| |
| if (new_state == DRAW_NO_BLOCK_PIN_UTIL) { |
| draw_reset_blk_colors(); |
| update_message(draw_state->default_message); |
| } |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_placement_macros(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| e_draw_placement_macros new_state = (enum e_draw_placement_macros)(((int)draw_state->show_placement_macros + 1) |
| % ((int)DRAW_PLACEMENT_MACROS_MAX)); |
| |
| draw_state->show_placement_macros = new_state; |
| |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_crit_path(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->pic_on_screen == PLACEMENT) { |
| switch (draw_state->show_crit_path) { |
| case DRAW_NO_CRIT_PATH: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES; |
| break; |
| case DRAW_CRIT_PATH_FLYLINES: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES_DELAYS; |
| break; |
| default: |
| draw_state->show_crit_path = DRAW_NO_CRIT_PATH; |
| break; |
| }; |
| } else { |
| VTR_ASSERT(draw_state->pic_on_screen == ROUTING); |
| |
| switch (draw_state->show_crit_path) { |
| case DRAW_NO_CRIT_PATH: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES; |
| break; |
| case DRAW_CRIT_PATH_FLYLINES: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES_DELAYS; |
| break; |
| case DRAW_CRIT_PATH_FLYLINES_DELAYS: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_ROUTING; |
| break; |
| case DRAW_CRIT_PATH_ROUTING: |
| draw_state->show_crit_path = DRAW_CRIT_PATH_ROUTING_DELAYS; |
| break; |
| default: |
| draw_state->show_crit_path = DRAW_NO_CRIT_PATH; |
| break; |
| }; |
| } |
| |
| drawscreen_ptr(); |
| } |
| |
| static void toggle_router_rr_costs(void (*drawscreen_ptr)()) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| e_draw_router_rr_cost new_state = (enum e_draw_router_rr_cost)(((int)draw_state->show_router_rr_cost + 1) |
| % ((int)DRAW_ROUTER_RR_COST_MAX)); |
| draw_state->show_router_rr_cost = new_state; |
| |
| if (draw_state->show_router_rr_cost == DRAW_NO_ROUTER_RR_COST) { |
| update_message(draw_state->default_message); |
| } |
| drawscreen_ptr(); |
| } |
| |
| void alloc_draw_structs(const t_arch* arch) { |
| /* Call accessor functions to retrieve global variables. */ |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| /* Allocate the structures needed to draw the placement and routing. Set * |
| * up the default colors for blocks and nets. */ |
| |
| draw_coords->tile_x = (float*)vtr::malloc(device_ctx.grid.width() * sizeof(float)); |
| draw_coords->tile_y = (float*)vtr::malloc(device_ctx.grid.height() * sizeof(float)); |
| |
| /* For sub-block drawings inside clbs */ |
| draw_internal_alloc_blk(); |
| |
| draw_state->net_color.resize(cluster_ctx.clb_nlist.nets().size()); |
| |
| draw_state->block_color.resize(cluster_ctx.clb_nlist.blocks().size()); |
| |
| /* Space is allocated for draw_rr_node but not initialized because we do * |
| * not yet know information about the routing resources. */ |
| draw_state->draw_rr_node = (t_draw_rr_node*)vtr::malloc( |
| device_ctx.rr_nodes.size() * sizeof(t_draw_rr_node)); |
| |
| draw_state->arch_info = arch; |
| |
| deselect_all(); /* Set initial colors */ |
| } |
| |
| void free_draw_structs() { |
| /* Free everything allocated by alloc_draw_structs. Called after close_graphics() * |
| * in vpr_api.c. |
| * |
| * For safety, set all the array pointers to NULL in case any data |
| * structure gets freed twice. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| if (draw_coords != nullptr) { |
| free(draw_coords->tile_x); |
| draw_coords->tile_x = nullptr; |
| free(draw_coords->tile_y); |
| draw_coords->tile_y = nullptr; |
| } |
| |
| if (draw_state != nullptr) { |
| free(draw_state->draw_rr_node); |
| draw_state->draw_rr_node = nullptr; |
| } |
| } |
| |
| void init_draw_coords(float width_val) { |
| /* Load the arrays containing the left and bottom coordinates of the clbs * |
| * forming the FPGA. tile_width_val sets the width and height of a drawn * |
| * clb. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| if (!draw_state->show_graphics) |
| return; /* Graphics disabled */ |
| |
| /* Each time routing is on screen, need to reallocate the color of each * |
| * rr_node, as the number of rr_nodes may change. */ |
| if (device_ctx.rr_nodes.size() != 0) { |
| draw_state->draw_rr_node = (t_draw_rr_node*)vtr::realloc(draw_state->draw_rr_node, |
| (device_ctx.rr_nodes.size()) * sizeof(t_draw_rr_node)); |
| for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { |
| draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; |
| draw_state->draw_rr_node[i].node_highlighted = false; |
| } |
| } |
| |
| draw_coords->tile_width = width_val; |
| draw_coords->pin_size = 0.3; |
| for (int i = 0; i < device_ctx.num_block_types; ++i) { |
| if (device_ctx.block_types[i].num_pins > 0) { |
| draw_coords->pin_size = min(draw_coords->pin_size, |
| (draw_coords->get_tile_width() / (4.0F * device_ctx.block_types[i].num_pins))); |
| } |
| } |
| |
| size_t j = 0; |
| for (size_t i = 0; i < (device_ctx.grid.width() - 1); i++) { |
| draw_coords->tile_x[i] = (i * draw_coords->get_tile_width()) + j; |
| j += device_ctx.chan_width.y_list[i] + 1; /* N wires need N+1 units of space */ |
| } |
| draw_coords->tile_x[device_ctx.grid.width() - 1] = ((device_ctx.grid.width() - 1) * draw_coords->get_tile_width()) + j; |
| |
| j = 0; |
| for (size_t i = 0; i < (device_ctx.grid.height() - 1); ++i) { |
| draw_coords->tile_y[i] = (i * draw_coords->get_tile_width()) + j; |
| j += device_ctx.chan_width.x_list[i] + 1; |
| } |
| draw_coords->tile_y[device_ctx.grid.height() - 1] = ((device_ctx.grid.height() - 1) * draw_coords->get_tile_width()) + j; |
| |
| /* Load coordinates of sub-blocks inside the clbs */ |
| draw_internal_init_blk(); |
| |
| //Margin beyond edge of the drawn device to extend the visible world |
| //Setting this to > 0.0 means 'Zoom Fit' leave some fraction of white |
| //space around the device edges |
| constexpr float VISIBLE_MARGIN = 0.01; |
| |
| float draw_width = draw_coords->tile_x[device_ctx.grid.width() - 1] + draw_coords->get_tile_width(); |
| float draw_height = draw_coords->tile_y[device_ctx.grid.height() - 1] + draw_coords->get_tile_width(); |
| |
| set_visible_world( |
| -VISIBLE_MARGIN * draw_width, |
| -VISIBLE_MARGIN * draw_height, |
| (1. + VISIBLE_MARGIN) * draw_width, |
| (1. + VISIBLE_MARGIN) * draw_height); |
| } |
| |
| /* Draws the blocks placed on the proper clbs. Occupied blocks are darker colours * |
| * while empty ones are lighter colours and have a dashed border. */ |
| static void drawplace() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& place_ctx = g_vpr_ctx.placement(); |
| |
| ClusterBlockId bnum; |
| int num_sub_tiles; |
| |
| setlinewidth(0); |
| |
| for (size_t i = 0; i < device_ctx.grid.width(); i++) { |
| for (size_t j = 0; j < device_ctx.grid.height(); j++) { |
| /* Only the first block of a group should control drawing */ |
| if (device_ctx.grid[i][j].width_offset > 0 || device_ctx.grid[i][j].height_offset > 0) |
| continue; |
| |
| num_sub_tiles = device_ctx.grid[i][j].type->capacity; |
| /* Don't draw if tile capacity is zero. eg. corners. */ |
| if (num_sub_tiles == 0) { |
| continue; |
| } |
| |
| for (int k = 0; k < num_sub_tiles; ++k) { |
| /* Look at the tile at start of large block */ |
| bnum = place_ctx.grid_blocks[i][j].blocks[k]; |
| |
| /* Fill background for the clb. Do not fill if "show_blk_internal" |
| * is toggled. |
| */ |
| if (bnum == INVALID_BLOCK_ID) continue; |
| |
| //Determine the block color |
| t_color block_color; |
| if (bnum != EMPTY_BLOCK_ID) { |
| block_color = draw_state->block_color[bnum]; |
| } else { |
| block_color = get_block_type_color(device_ctx.grid[i][j].type); |
| block_color = lighten_color(block_color, EMPTY_BLOCK_LIGHTEN_FACTOR); |
| } |
| setcolor(block_color); |
| |
| /* Get coords of current sub_tile */ |
| t_bound_box abs_clb_bbox = draw_coords->get_absolute_clb_bbox(i, j, k); |
| |
| fillrect(abs_clb_bbox); |
| |
| setcolor(BLACK); |
| |
| setlinestyle((EMPTY_BLOCK_ID == bnum) ? DASHED : SOLID); |
| drawrect(abs_clb_bbox); |
| |
| /* Draw text if the space has parts of the netlist */ |
| if (bnum != EMPTY_BLOCK_ID && bnum != INVALID_BLOCK_ID) { |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| std::string name = cluster_ctx.clb_nlist.block_name(bnum) + vtr::string_fmt(" (#%zu)", size_t(bnum)); |
| drawtext_in(abs_clb_bbox, name.c_str()); |
| } |
| |
| /* Draw text for block type so that user knows what block */ |
| if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { |
| std::string block_type_loc = device_ctx.grid[i][j].type->name; |
| block_type_loc += vtr::string_fmt(" (%d,%d)", i, j); |
| drawtext(abs_clb_bbox.get_center() - t_point(0, abs_clb_bbox.get_height() / 4), |
| block_type_loc.c_str(), abs_clb_bbox); |
| } |
| } |
| } |
| } |
| } |
| |
| static void drawnets() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| /* This routine draws the nets on the placement. The nets have not * |
| * yet been routed, so we just draw a chain showing a possible path * |
| * for each net. This gives some idea of future congestion. */ |
| |
| ClusterBlockId b1, b2; |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| setlinestyle(SOLID); |
| setlinewidth(0); |
| |
| /* Draw the net as a star from the source to each sink. Draw from centers of * |
| * blocks (or sub blocks in the case of IOs). */ |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| if (cluster_ctx.clb_nlist.net_is_ignored(net_id)) |
| continue; /* Don't draw */ |
| |
| setcolor(draw_state->net_color[net_id]); |
| b1 = cluster_ctx.clb_nlist.net_driver_block(net_id); |
| t_point driver_center = draw_coords->get_absolute_clb_bbox(b1, cluster_ctx.clb_nlist.block_type(b1)).get_center(); |
| |
| for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { |
| b2 = cluster_ctx.clb_nlist.pin_block(pin_id); |
| t_point sink_center = draw_coords->get_absolute_clb_bbox(b2, cluster_ctx.clb_nlist.block_type(b2)).get_center(); |
| drawline(driver_center, sink_center); |
| |
| /* Uncomment to draw a chain instead of a star. */ |
| /* driver_center = sink_center; */ |
| } |
| } |
| } |
| |
| static void draw_congestion() { |
| /* Draws all the overused routing resources (i.e. congestion) in RED. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->show_congestion == DRAW_NO_CONGEST) { |
| return; |
| } |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| //Record min/max congestion |
| float min_congestion_ratio = 1.; |
| float max_congestion_ratio = min_congestion_ratio; |
| std::vector<int> congested_rr_nodes = collect_congested_rr_nodes(); |
| for (int inode : congested_rr_nodes) { |
| short occ = route_ctx.rr_node_route_inf[inode].occ(); |
| short capacity = device_ctx.rr_nodes[inode].capacity(); |
| |
| float congestion_ratio = float(occ) / capacity; |
| |
| max_congestion_ratio = std::max(max_congestion_ratio, congestion_ratio); |
| } |
| |
| char msg[vtr::bufsize]; |
| if (draw_state->show_congestion == DRAW_CONGESTED) { |
| sprintf(msg, "RR Node Overuse ratio range (%.2f, %.2f]", min_congestion_ratio, max_congestion_ratio); |
| } else { |
| VTR_ASSERT(draw_state->show_congestion == DRAW_CONGESTED_WITH_NETS); |
| sprintf(msg, "RR Node Overuse ratio range (%.2f, %.2f] (and congested nets)", min_congestion_ratio, max_congestion_ratio); |
| } |
| update_message(msg); |
| |
| std::unique_ptr<vtr::ColorMap> cmap = std::make_unique<vtr::PlasmaColorMap>(min_congestion_ratio, max_congestion_ratio); |
| |
| //Sort the nodes in ascending order of value for drawing, this ensures high |
| //valued nodes are not overdrawn by lower value ones (e.g. when zoomed-out far) |
| auto cmp_ascending_acc_cost = [&](int lhs_node, int rhs_node) { |
| short lhs_occ = route_ctx.rr_node_route_inf[lhs_node].occ(); |
| short lhs_capacity = device_ctx.rr_nodes[lhs_node].capacity(); |
| |
| short rhs_occ = route_ctx.rr_node_route_inf[rhs_node].occ(); |
| short rhs_capacity = device_ctx.rr_nodes[rhs_node].capacity(); |
| |
| float lhs_cong_ratio = float(lhs_occ) / lhs_capacity; |
| float rhs_cong_ratio = float(rhs_occ) / rhs_capacity; |
| |
| return lhs_cong_ratio < rhs_cong_ratio; |
| }; |
| std::sort(congested_rr_nodes.begin(), congested_rr_nodes.end(), cmp_ascending_acc_cost); |
| |
| if (draw_state->show_congestion == DRAW_CONGESTED_WITH_NETS) { |
| auto rr_node_nets = collect_rr_node_nets(); |
| |
| for (int inode : congested_rr_nodes) { |
| for (ClusterNetId net : rr_node_nets[inode]) { |
| t_color color = kelly_max_contrast_colors[size_t(net) % kelly_max_contrast_colors.size()]; |
| draw_state->net_color[net] = color; |
| } |
| } |
| setlinewidth(0); |
| drawroute(HIGHLIGHTED); |
| |
| //Reset colors |
| for (int inode : congested_rr_nodes) { |
| for (ClusterNetId net : rr_node_nets[inode]) { |
| draw_state->net_color[net] = DEFAULT_RR_NODE_COLOR; |
| } |
| } |
| } else { |
| setlinewidth(2); |
| } |
| |
| //Draw each congested node |
| for (int inode : congested_rr_nodes) { |
| short occ = route_ctx.rr_node_route_inf[inode].occ(); |
| short capacity = device_ctx.rr_nodes[inode].capacity(); |
| |
| float congestion_ratio = float(occ) / capacity; |
| |
| bool node_congested = (occ > capacity); |
| VTR_ASSERT(node_congested); |
| |
| t_color color = to_t_color(cmap->color(congestion_ratio)); |
| |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case CHANX: //fallthrough |
| case CHANY: |
| draw_rr_chan(inode, color); |
| break; |
| |
| case IPIN: //fallthrough |
| case OPIN: |
| draw_rr_pin(inode, color); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| draw_state->color_map = std::move(cmap); |
| } |
| |
| static void draw_routing_costs() { |
| /* Draws routing costs */ |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->show_routing_costs == DRAW_NO_ROUTING_COSTS) { |
| return; |
| } |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| setlinewidth(2); |
| |
| VTR_ASSERT(!route_ctx.rr_node_route_inf.empty()); |
| |
| float min_cost = std::numeric_limits<float>::infinity(); |
| float max_cost = -min_cost; |
| std::vector<float> rr_node_costs(device_ctx.rr_nodes.size(), 0.); |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| float cost = 0.; |
| if (draw_state->show_routing_costs == DRAW_TOTAL_ROUTING_COSTS |
| || draw_state->show_routing_costs == DRAW_LOG_TOTAL_ROUTING_COSTS) { |
| int cost_index = device_ctx.rr_nodes[inode].cost_index(); |
| cost = device_ctx.rr_indexed_data[cost_index].base_cost |
| + route_ctx.rr_node_route_inf[inode].acc_cost |
| + route_ctx.rr_node_route_inf[inode].pres_cost; |
| |
| } else if (draw_state->show_routing_costs == DRAW_BASE_ROUTING_COSTS) { |
| int cost_index = device_ctx.rr_nodes[inode].cost_index(); |
| cost = device_ctx.rr_indexed_data[cost_index].base_cost; |
| |
| } else if (draw_state->show_routing_costs == DRAW_ACC_ROUTING_COSTS |
| || draw_state->show_routing_costs == DRAW_LOG_ACC_ROUTING_COSTS) { |
| cost = route_ctx.rr_node_route_inf[inode].acc_cost; |
| |
| } else { |
| VTR_ASSERT(draw_state->show_routing_costs == DRAW_PRES_ROUTING_COSTS |
| || draw_state->show_routing_costs == DRAW_LOG_PRES_ROUTING_COSTS); |
| cost = route_ctx.rr_node_route_inf[inode].pres_cost; |
| } |
| |
| if (draw_state->show_routing_costs == DRAW_LOG_TOTAL_ROUTING_COSTS |
| || draw_state->show_routing_costs == DRAW_LOG_ACC_ROUTING_COSTS |
| || draw_state->show_routing_costs == DRAW_LOG_PRES_ROUTING_COSTS) { |
| cost = std::log(cost); |
| } |
| rr_node_costs[inode] = cost; |
| min_cost = std::min(min_cost, cost); |
| max_cost = std::max(max_cost, cost); |
| } |
| |
| //Hide min value, draw_rr_costs() ignores NaN's |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| if (rr_node_costs[inode] == min_cost) { |
| rr_node_costs[inode] = NAN; |
| } |
| } |
| |
| char msg[vtr::bufsize]; |
| if (draw_state->show_routing_costs == DRAW_TOTAL_ROUTING_COSTS) { |
| sprintf(msg, "Total Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_LOG_TOTAL_ROUTING_COSTS) { |
| sprintf(msg, "Log Total Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_BASE_ROUTING_COSTS) { |
| sprintf(msg, "Base Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_ACC_ROUTING_COSTS) { |
| sprintf(msg, "Accumulated (Historical) Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_LOG_ACC_ROUTING_COSTS) { |
| sprintf(msg, "Log Accumulated (Historical) Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_PRES_ROUTING_COSTS) { |
| sprintf(msg, "Present Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else if (draw_state->show_routing_costs == DRAW_LOG_PRES_ROUTING_COSTS) { |
| sprintf(msg, "Log Present Congestion Cost Range [%g, %g]", min_cost, max_cost); |
| } else { |
| sprintf(msg, "Cost Range [%g, %g]", min_cost, max_cost); |
| } |
| update_message(msg); |
| |
| draw_rr_costs(rr_node_costs, true); |
| } |
| |
| static void draw_routing_bb() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->show_routing_bb == OPEN) { |
| return; |
| } |
| |
| auto& route_ctx = g_vpr_ctx.routing(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| VTR_ASSERT(draw_state->show_routing_bb != OPEN); |
| VTR_ASSERT(draw_state->show_routing_bb < (int)route_ctx.route_bb.size()); |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| auto net_id = ClusterNetId(draw_state->show_routing_bb); |
| const t_bb* bb = &route_ctx.route_bb[net_id]; |
| |
| //The router considers an RR node to be 'within' the the bounding box if it |
| //is *loosely* greater (i.e. greater than or equal) the left/bottom edges, and |
| //it is *loosely* less (i.e. less than or equal) the right/top edges. |
| // |
| //In the graphics we represent this by drawing the BB so that legal RR node start/end points |
| //are contained within the drawn box. Since VPR associates each x/y channel location to |
| //the right/top of the tile with the same x/y cordinates, this means we draw the box so that: |
| // * The left edge is to the left of the channel at bb xmin (including the channel at xmin) |
| // * The bottom edge is to the below of the channel at bb ymin (including the channel at ymin) |
| // * The right edge is to the right of the channel at bb xmax (including the channel at xmax) |
| // * The top edge is to the right of the channel at bb ymax (including the channel at ymax) |
| //Since tile_x/tile_y correspond to the drawing coordinates the block at grid x/y's bottom-left corner |
| //this means we need to shift the top/right drawn co-ordinate one tile + channel width right/up so |
| //the drawn box contains the top/right channels |
| int draw_xlow = draw_coords->tile_x[bb->xmin]; |
| int draw_ylow = draw_coords->tile_y[bb->ymin]; |
| int draw_xhigh = draw_coords->tile_x[bb->xmax] + 2 * draw_coords->get_tile_width(); |
| int draw_yhigh = draw_coords->tile_y[bb->ymax] + 2 * draw_coords->get_tile_height(); |
| |
| setcolor(RED); |
| drawrect(draw_xlow, draw_ylow, draw_xhigh, draw_yhigh); |
| |
| t_color fill = SKYBLUE; |
| fill.alpha *= 0.3; |
| setcolor(fill); |
| fillrect(draw_xlow, draw_ylow, draw_xhigh, draw_yhigh); |
| |
| draw_routed_net(net_id); |
| |
| std::string msg; |
| ; |
| msg += "Showing BB"; |
| msg += " (" + std::to_string(bb->xmin) + ", " + std::to_string(bb->ymin) + ", " + std::to_string(bb->xmax) + ", " + std::to_string(bb->ymax) + ")"; |
| msg += " and routing for net '" + cluster_ctx.clb_nlist.net_name(net_id) + "'"; |
| msg += " (#" + std::to_string(size_t(net_id)) + ")"; |
| update_message(msg.c_str()); |
| } |
| |
| void draw_rr() { |
| /* Draws the routing resources that exist in the FPGA, if the user wants * |
| * them drawn. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| if (draw_state->draw_rr_toggle == DRAW_NO_RR || draw_state->draw_rr_toggle == DRAW_MOUSE_OVER_RR) { |
| setlinewidth(3); |
| drawroute(HIGHLIGHTED); |
| setlinewidth(0); |
| return; |
| } |
| |
| setlinestyle(SOLID); |
| |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| if (!draw_state->draw_rr_node[inode].node_highlighted) { |
| /* If not highlighted node, assign color based on type. */ |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case CHANX: |
| case CHANY: |
| draw_state->draw_rr_node[inode].color = DEFAULT_RR_NODE_COLOR; |
| break; |
| case OPIN: |
| draw_state->draw_rr_node[inode].color = PINK; |
| break; |
| case IPIN: |
| draw_state->draw_rr_node[inode].color = LIGHTSKYBLUE; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Now call drawing routines to draw the node. */ |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case SOURCE: |
| case SINK: |
| break; /* Don't draw. */ |
| |
| case CHANX: |
| draw_rr_chan(inode, draw_state->draw_rr_node[inode].color); |
| draw_rr_edges(inode); |
| break; |
| |
| case CHANY: |
| draw_rr_chan(inode, draw_state->draw_rr_node[inode].color); |
| draw_rr_edges(inode); |
| break; |
| |
| case IPIN: |
| draw_rr_pin(inode, draw_state->draw_rr_node[inode].color); |
| break; |
| |
| case OPIN: |
| draw_rr_pin(inode, draw_state->draw_rr_node[inode].color); |
| draw_rr_edges(inode); |
| break; |
| |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_rr: Unexpected rr_node type: %d.\n", device_ctx.rr_nodes[inode].type()); |
| } |
| } |
| |
| drawroute(HIGHLIGHTED); |
| } |
| |
| static void draw_rr_chan(int inode, const t_color color) { |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| t_rr_type type = device_ctx.rr_nodes[inode].type(); |
| |
| VTR_ASSERT(type == CHANX || type == CHANY); |
| |
| t_bound_box bound_box = draw_get_rr_chan_bbox(inode); |
| e_direction dir = device_ctx.rr_nodes[inode].direction(); |
| |
| //We assume increasing direction, and swap if needed |
| t_point start = bound_box.bottom_left(); |
| t_point end = bound_box.top_right(); |
| if (dir == DEC_DIRECTION) { |
| std::swap(start, end); |
| } |
| |
| setcolor(color); |
| if (color != DEFAULT_RR_NODE_COLOR) { |
| // If wire is highlighted, then draw with thicker linewidth. |
| setlinewidth(3); |
| } |
| |
| drawline(start, end); |
| |
| if (color != DEFAULT_RR_NODE_COLOR) { |
| // Revert width change |
| setlinewidth(0); |
| } |
| |
| // draw the arrows and small lines iff zoomed in really far. |
| if (default_triangle_LOD_screen_area_test() == false) { |
| return; |
| } |
| |
| e_side mux_dir = TOP; |
| int coord_min = -1; |
| int coord_max = -1; |
| if (type == CHANX) { |
| coord_min = device_ctx.rr_nodes[inode].xlow(); |
| coord_max = device_ctx.rr_nodes[inode].xhigh(); |
| if (dir == INC_DIRECTION) { |
| mux_dir = RIGHT; |
| } else { |
| mux_dir = LEFT; |
| } |
| } else { |
| VTR_ASSERT(type == CHANY); |
| coord_min = device_ctx.rr_nodes[inode].ylow(); |
| coord_max = device_ctx.rr_nodes[inode].yhigh(); |
| if (dir == INC_DIRECTION) { |
| mux_dir = TOP; |
| } else { |
| mux_dir = BOTTOM; |
| } |
| } |
| |
| //Draw direction indicators at the boundary of each switch block, and label them |
| //with the corresponding switch point (see build_switchblocks.c for a description of switch points) |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| float arrow_offset = DEFAULT_ARROW_SIZE / 2; |
| t_color arrow_color = LIGHTGREY; |
| t_color text_color = BLACK; |
| for (int k = coord_min; k <= coord_max; ++k) { |
| int switchpoint_min = -1; |
| int switchpoint_max = -1; |
| if (dir == INC_DIRECTION) { |
| switchpoint_min = k - coord_min; |
| switchpoint_max = switchpoint_min + 1; |
| } else { |
| switchpoint_min = (coord_max + 1) - k; |
| switchpoint_max = switchpoint_min - 1; |
| } |
| |
| t_point arrow_loc_min; |
| t_point arrow_loc_max; |
| if (type == CHANX) { |
| float sb_xmin = draw_coords->tile_x[k]; |
| arrow_loc_min = t_point(sb_xmin + arrow_offset, start.y); |
| |
| float sb_xmax = draw_coords->tile_x[k] + draw_coords->get_tile_width(); |
| arrow_loc_max = t_point(sb_xmax - arrow_offset, start.y); |
| |
| } else { |
| float sb_ymin = draw_coords->tile_y[k]; |
| arrow_loc_min = t_point(start.x, sb_ymin + arrow_offset); |
| |
| float sb_ymax = draw_coords->tile_y[k] + draw_coords->get_tile_height(); |
| arrow_loc_max = t_point(start.x, sb_ymax - arrow_offset); |
| } |
| |
| if (switchpoint_min == 0) { |
| if (dir != BI_DIRECTION) { |
| //Draw a mux at the start of each wire, labelled with it's size (#inputs) |
| draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_nodes[inode].fan_in()); |
| } |
| } else { |
| //Draw arrows and label with switch point |
| if (k == coord_min) { |
| std::swap(arrow_color, text_color); |
| } |
| |
| setcolor(arrow_color); |
| draw_triangle_along_line(arrow_loc_min, start, end); |
| |
| setcolor(text_color); |
| t_bound_box bbox(t_point(arrow_loc_min.x - DEFAULT_ARROW_SIZE / 2, arrow_loc_min.y - DEFAULT_ARROW_SIZE / 4), |
| t_point(arrow_loc_min.x + DEFAULT_ARROW_SIZE / 2, arrow_loc_min.y + DEFAULT_ARROW_SIZE / 4)); |
| drawtext_in(bbox, std::to_string(switchpoint_min)); |
| |
| if (k == coord_min) { |
| //Revert |
| std::swap(arrow_color, text_color); |
| } |
| } |
| |
| if (switchpoint_max == 0) { |
| if (dir != BI_DIRECTION) { |
| //Draw a mux at the start of each wire, labelled with it's size (#inputs) |
| draw_mux_with_size(start, mux_dir, WIRE_DRAWING_WIDTH, device_ctx.rr_nodes[inode].fan_in()); |
| } |
| } else { |
| //Draw arrows and label with switch point |
| if (k == coord_max) { |
| std::swap(arrow_color, text_color); |
| } |
| |
| setcolor(arrow_color); |
| draw_triangle_along_line(arrow_loc_max, start, end); |
| |
| setcolor(text_color); |
| t_bound_box bbox(t_point(arrow_loc_max.x - DEFAULT_ARROW_SIZE / 2, arrow_loc_max.y - DEFAULT_ARROW_SIZE / 4), |
| t_point(arrow_loc_max.x + DEFAULT_ARROW_SIZE / 2, arrow_loc_max.y + DEFAULT_ARROW_SIZE / 4)); |
| drawtext_in(bbox, std::to_string(switchpoint_max)); |
| |
| if (k == coord_max) { |
| //Revert |
| std::swap(arrow_color, text_color); |
| } |
| } |
| } |
| setcolor(color); //Ensure color is still set correctly if we drew any arrows/text |
| } |
| |
| static void draw_rr_edges(int inode) { |
| /* Draws all the edges that the user wants shown between inode and what it * |
| * connects to. inode is assumed to be a CHANX, CHANY, or IPIN. */ |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| t_rr_type from_type, to_type; |
| int to_node, from_ptc_num, to_ptc_num; |
| short switch_type; |
| |
| from_type = device_ctx.rr_nodes[inode].type(); |
| |
| if ((draw_state->draw_rr_toggle == DRAW_NODES_RR) |
| || (draw_state->draw_rr_toggle == DRAW_NODES_AND_SBOX_RR && from_type == OPIN)) { |
| return; /* Nothing to draw. */ |
| } |
| |
| from_ptc_num = device_ctx.rr_nodes[inode].ptc_num(); |
| |
| for (int iedge = 0, l = device_ctx.rr_nodes[inode].num_edges(); iedge < l; iedge++) { |
| to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); |
| to_type = device_ctx.rr_nodes[to_node].type(); |
| to_ptc_num = device_ctx.rr_nodes[to_node].ptc_num(); |
| bool edge_configurable = device_ctx.rr_nodes[inode].edge_is_configurable(iedge); |
| |
| switch (from_type) { |
| case OPIN: |
| switch (to_type) { |
| case CHANX: |
| case CHANY: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| // If OPIN was clicked on, set color to fan-out |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| // If CHANX or CHANY got clicked, set color to fan-in |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else { |
| setcolor(PINK); |
| } |
| draw_pin_to_chan_edge(inode, to_node); |
| break; |
| case IPIN: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else { |
| setcolor(MEDIUMPURPLE); |
| } |
| draw_pin_to_pin(inode, to_node); |
| break; |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_rr_edges: node %d (type: %d) connects to node %d (type: %d).\n", |
| inode, from_type, to_node, to_type); |
| break; |
| } |
| break; |
| |
| case CHANX: /* from_type */ |
| switch (to_type) { |
| case IPIN: |
| if (draw_state->draw_rr_toggle == DRAW_NODES_AND_SBOX_RR) { |
| break; |
| } |
| |
| if (draw_state->draw_rr_node[to_node].node_highlighted && draw_state->draw_rr_node[inode].color == DEFAULT_RR_NODE_COLOR) { |
| // If the IPIN is clicked on, draw connection to all the CHANX |
| // wire segments fanning into the pin. If a CHANX wire is clicked |
| // on, draw only the connection between that wire and the IPIN, with |
| // the pin fanning out from the wire. |
| break; |
| } |
| |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else { |
| setcolor(LIGHTSKYBLUE); |
| } |
| draw_pin_to_chan_edge(to_node, inode); |
| break; |
| |
| case CHANX: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else if (!edge_configurable) { |
| setcolor(DARKGREY); |
| } else { |
| setcolor(DARKGREEN); |
| } |
| switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); |
| draw_chanx_to_chanx_edge(inode, to_node, |
| to_ptc_num, switch_type); |
| break; |
| |
| case CHANY: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else if (!edge_configurable) { |
| setcolor(DARKGREY); |
| } else { |
| setcolor(DARKGREEN); |
| } |
| switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); |
| draw_chanx_to_chany_edge(inode, from_ptc_num, to_node, |
| to_ptc_num, FROM_X_TO_Y, switch_type); |
| break; |
| |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_rr_edges: node %d (type: %d) connects to node %d (type: %d).\n", |
| inode, from_type, to_node, to_type); |
| break; |
| } |
| break; |
| |
| case CHANY: /* from_type */ |
| switch (to_type) { |
| case IPIN: |
| if (draw_state->draw_rr_toggle == DRAW_NODES_AND_SBOX_RR) { |
| break; |
| } |
| |
| if (draw_state->draw_rr_node[to_node].node_highlighted && draw_state->draw_rr_node[inode].color == DEFAULT_RR_NODE_COLOR) { |
| // If the IPIN is clicked on, draw connection to all the CHANY |
| // wire segments fanning into the pin. If a CHANY wire is clicked |
| // on, draw only the connection between that wire and the IPIN, with |
| // the pin fanning out from the wire. |
| break; |
| } |
| |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else { |
| setcolor(LIGHTSKYBLUE); |
| } |
| draw_pin_to_chan_edge(to_node, inode); |
| break; |
| |
| case CHANX: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else if (!edge_configurable) { |
| setcolor(DARKGREY); |
| } else { |
| setcolor(DARKGREEN); |
| } |
| switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); |
| draw_chanx_to_chany_edge(to_node, to_ptc_num, inode, |
| from_ptc_num, FROM_Y_TO_X, switch_type); |
| break; |
| |
| case CHANY: |
| if (draw_state->draw_rr_node[inode].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[to_node].color); |
| } else if (draw_state->draw_rr_node[to_node].color == MAGENTA) { |
| setcolor(draw_state->draw_rr_node[inode].color); |
| } else if (!edge_configurable) { |
| setcolor(DARKGREY); |
| } else { |
| setcolor(DARKGREEN); |
| } |
| switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge); |
| draw_chany_to_chany_edge(inode, to_node, |
| to_ptc_num, switch_type); |
| break; |
| |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_rr_edges: node %d (type: %d) connects to node %d (type: %d).\n", |
| inode, from_type, to_node, to_type); |
| break; |
| } |
| break; |
| |
| default: /* from_type */ |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "draw_rr_edges called with node %d of type %d.\n", |
| inode, from_type); |
| break; |
| } |
| } /* End of for each edge loop */ |
| } |
| |
| static void draw_x(float x, float y, float size) { |
| /* Draws an X centered at (x,y). The width and height of the X are each * |
| * 2 * size. */ |
| |
| drawline(x - size, y + size, x + size, y - size); |
| drawline(x - size, y - size, x + size, y + size); |
| } |
| |
| static void draw_chanx_to_chany_edge(int chanx_node, int chanx_track, int chany_node, int chany_track, enum e_edge_dir edge_dir, short switch_type) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| /* Draws an edge (SBOX connection) between an x-directed channel and a * |
| * y-directed channel. */ |
| |
| float x1, y1, x2, y2; |
| t_bound_box chanx_bbox, chany_bbox; |
| int chanx_xlow, chany_x, chany_ylow, chanx_y; |
| |
| /* Get the coordinates of the CHANX and CHANY segments. */ |
| chanx_bbox = draw_get_rr_chan_bbox(chanx_node); |
| chany_bbox = draw_get_rr_chan_bbox(chany_node); |
| |
| /* (x1,y1): point on CHANX segment, (x2,y2): point on CHANY segment. */ |
| |
| y1 = chanx_bbox.bottom(); |
| x2 = chany_bbox.left(); |
| |
| chanx_xlow = device_ctx.rr_nodes[chanx_node].xlow(); |
| chanx_y = device_ctx.rr_nodes[chanx_node].ylow(); |
| chany_x = device_ctx.rr_nodes[chany_node].xlow(); |
| chany_ylow = device_ctx.rr_nodes[chany_node].ylow(); |
| |
| if (chanx_xlow <= chany_x) { /* Can draw connection going right */ |
| /* Connection not at end of the CHANX segment. */ |
| x1 = draw_coords->tile_x[chany_x] + draw_coords->get_tile_width(); |
| |
| if (device_ctx.rr_nodes[chanx_node].direction() != BI_DIRECTION) { |
| if (edge_dir == FROM_X_TO_Y) { |
| if ((chanx_track % 2) == 1) { /* If dec wire, then going left */ |
| x1 = draw_coords->tile_x[chany_x + 1]; |
| } |
| } |
| } |
| |
| } else { /* Must draw connection going left. */ |
| x1 = chanx_bbox.left(); |
| } |
| |
| if (chany_ylow <= chanx_y) { /* Can draw connection going up. */ |
| /* Connection not at end of the CHANY segment. */ |
| y2 = draw_coords->tile_y[chanx_y] + draw_coords->get_tile_width(); |
| |
| if (device_ctx.rr_nodes[chany_node].direction() != BI_DIRECTION) { |
| if (edge_dir == FROM_Y_TO_X) { |
| if ((chany_track % 2) == 1) { /* If dec wire, then going down */ |
| y2 = draw_coords->tile_y[chanx_y + 1]; |
| } |
| } |
| } |
| |
| } else { /* Must draw connection going down. */ |
| y2 = chany_bbox.bottom(); |
| } |
| |
| drawline(x1, y1, x2, y2); |
| |
| if (draw_state->draw_rr_toggle == DRAW_ALL_RR || draw_state->draw_rr_node[chanx_node].node_highlighted) { |
| if (edge_dir == FROM_X_TO_Y) { |
| draw_rr_switch(x1, y1, x2, y2, device_ctx.rr_switch_inf[switch_type].buffered(), device_ctx.rr_switch_inf[switch_type].configurable()); |
| } else { |
| draw_rr_switch(x2, y2, x1, y1, device_ctx.rr_switch_inf[switch_type].buffered(), device_ctx.rr_switch_inf[switch_type].configurable()); |
| } |
| } |
| } |
| |
| static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type) { |
| /* Draws a connection between two x-channel segments. Passing in the track * |
| * numbers allows this routine to be used for both rr_graph and routing * |
| * drawing. */ |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| float x1, x2, y1, y2; |
| t_bound_box from_chan, to_chan; |
| int from_xlow, to_xlow, from_xhigh, to_xhigh; |
| |
| // Get the coordinates of the channel wires. |
| from_chan = draw_get_rr_chan_bbox(from_node); |
| to_chan = draw_get_rr_chan_bbox(to_node); |
| |
| /* (x1, y1) point on from_node, (x2, y2) point on to_node. */ |
| |
| y1 = from_chan.bottom(); |
| y2 = to_chan.bottom(); |
| |
| from_xlow = device_ctx.rr_nodes[from_node].xlow(); |
| from_xhigh = device_ctx.rr_nodes[from_node].xhigh(); |
| to_xlow = device_ctx.rr_nodes[to_node].xlow(); |
| to_xhigh = device_ctx.rr_nodes[to_node].xhigh(); |
| |
| if (to_xhigh < from_xlow) { /* From right to left */ |
| /* UDSD Note by WMF: could never happen for INC wires, unless U-turn. For DEC |
| * wires this handles well */ |
| x1 = from_chan.left(); |
| x2 = to_chan.right(); |
| } else if (to_xlow > from_xhigh) { /* From left to right */ |
| /* UDSD Note by WMF: could never happen for DEC wires, unless U-turn. For INC |
| * wires this handles well */ |
| x1 = from_chan.right(); |
| x2 = to_chan.left(); |
| } |
| |
| /* Segments overlap in the channel. Figure out best way to draw. Have to * |
| * make sure the drawing is symmetric in the from rr and to rr so the edges * |
| * will be drawn on top of each other for bidirectional connections. */ |
| |
| else { |
| if (device_ctx.rr_nodes[to_node].direction() != BI_DIRECTION) { |
| /* must connect to to_node's wire beginning at x2 */ |
| if (to_track % 2 == 0) { /* INC wire starts at leftmost edge */ |
| VTR_ASSERT(from_xlow < to_xlow); |
| x2 = to_chan.left(); |
| /* since no U-turns from_track must be INC as well */ |
| x1 = draw_coords->tile_x[to_xlow - 1] + draw_coords->get_tile_width(); |
| } else { /* DEC wire starts at rightmost edge */ |
| VTR_ASSERT(from_xhigh > to_xhigh); |
| x2 = to_chan.right(); |
| x1 = draw_coords->tile_x[to_xhigh + 1]; |
| } |
| } else { |
| if (to_xlow < from_xlow) { /* Draw from left edge of one to other */ |
| x1 = from_chan.left(); |
| x2 = draw_coords->tile_x[from_xlow - 1] + draw_coords->get_tile_width(); |
| } else if (from_xlow < to_xlow) { |
| x1 = draw_coords->tile_x[to_xlow - 1] + draw_coords->get_tile_width(); |
| x2 = to_chan.left(); |
| } /* The following then is executed when from_xlow == to_xlow */ |
| else if (to_xhigh > from_xhigh) { /* Draw from right edge of one to other */ |
| x1 = from_chan.right(); |
| x2 = draw_coords->tile_x[from_xhigh + 1]; |
| } else if (from_xhigh > to_xhigh) { |
| x1 = draw_coords->tile_x[to_xhigh + 1]; |
| x2 = to_chan.right(); |
| } else { /* Complete overlap: start and end both align. Draw outside the sbox */ |
| x1 = from_chan.left(); |
| x2 = from_chan.left() + draw_coords->get_tile_width(); |
| } |
| } |
| } |
| |
| drawline(x1, y1, x2, y2); |
| |
| if (draw_state->draw_rr_toggle == DRAW_ALL_RR || draw_state->draw_rr_node[from_node].node_highlighted) { |
| draw_rr_switch(x1, y1, x2, y2, device_ctx.rr_switch_inf[switch_type].buffered(), device_ctx.rr_switch_inf[switch_type].configurable()); |
| } |
| } |
| |
| static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| /* Draws a connection between two y-channel segments. Passing in the track * |
| * numbers allows this routine to be used for both rr_graph and routing * |
| * drawing. */ |
| |
| float x1, x2, y1, y2; |
| t_bound_box from_chan, to_chan; |
| int from_ylow, to_ylow, from_yhigh, to_yhigh; //, from_x, to_x; |
| |
| // Get the coordinates of the channel wires. |
| from_chan = draw_get_rr_chan_bbox(from_node); |
| to_chan = draw_get_rr_chan_bbox(to_node); |
| |
| // from_x = device_ctx.rr_nodes[from_node].xlow(); |
| // to_x = device_ctx.rr_nodes[to_node].xlow(); |
| from_ylow = device_ctx.rr_nodes[from_node].ylow(); |
| from_yhigh = device_ctx.rr_nodes[from_node].yhigh(); |
| to_ylow = device_ctx.rr_nodes[to_node].ylow(); |
| to_yhigh = device_ctx.rr_nodes[to_node].yhigh(); |
| |
| /* (x1, y1) point on from_node, (x2, y2) point on to_node. */ |
| |
| x1 = from_chan.left(); |
| x2 = to_chan.left(); |
| |
| if (to_yhigh < from_ylow) { /* From upper to lower */ |
| y1 = from_chan.bottom(); |
| y2 = to_chan.top(); |
| } else if (to_ylow > from_yhigh) { /* From lower to upper */ |
| y1 = from_chan.top(); |
| y2 = to_chan.bottom(); |
| } |
| |
| /* Segments overlap in the channel. Figure out best way to draw. Have to * |
| * make sure the drawing is symmetric in the from rr and to rr so the edges * |
| * will be drawn on top of each other for bidirectional connections. */ |
| |
| /* UDSD Modification by WMF Begin */ |
| else { |
| if (device_ctx.rr_nodes[to_node].direction() != BI_DIRECTION) { |
| if (to_track % 2 == 0) { /* INC wire starts at bottom edge */ |
| |
| y2 = to_chan.bottom(); |
| /* since no U-turns from_track must be INC as well */ |
| y1 = draw_coords->tile_y[to_ylow - 1] + draw_coords->get_tile_width(); |
| } else { /* DEC wire starts at top edge */ |
| |
| y2 = to_chan.top(); |
| y1 = draw_coords->tile_y[to_yhigh + 1]; |
| } |
| } else { |
| if (to_ylow < from_ylow) { /* Draw from bottom edge of one to other. */ |
| y1 = from_chan.bottom(); |
| y2 = draw_coords->tile_y[from_ylow - 1] + draw_coords->get_tile_width(); |
| } else if (from_ylow < to_ylow) { |
| y1 = draw_coords->tile_y[to_ylow - 1] + draw_coords->get_tile_width(); |
| y2 = to_chan.bottom(); |
| } else if (to_yhigh > from_yhigh) { /* Draw from top edge of one to other. */ |
| y1 = from_chan.top(); |
| y2 = draw_coords->tile_y[from_yhigh + 1]; |
| } else if (from_yhigh > to_yhigh) { |
| y1 = draw_coords->tile_y[to_yhigh + 1]; |
| y2 = to_chan.top(); |
| } else { /* Complete overlap: start and end both align. Draw outside the sbox */ |
| y1 = from_chan.bottom(); |
| y2 = from_chan.bottom() + draw_coords->get_tile_width(); |
| } |
| } |
| } |
| |
| /* UDSD Modification by WMF End */ |
| drawline(x1, y1, x2, y2); |
| |
| if (draw_state->draw_rr_toggle == DRAW_ALL_RR || draw_state->draw_rr_node[from_node].node_highlighted) { |
| draw_rr_switch(x1, y1, x2, y2, device_ctx.rr_switch_inf[switch_type].buffered(), device_ctx.rr_switch_inf[switch_type].configurable()); |
| } |
| } |
| |
| /* This function computes and returns the boundary coordinates of a channel |
| * wire segment. This can be used for drawing a wire or determining if a |
| * wire has been clicked on by the user. |
| * TODO: Fix this for global routing, currently for detailed only. |
| */ |
| static t_bound_box draw_get_rr_chan_bbox(int inode) { |
| t_bound_box bound_box; |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case CHANX: |
| bound_box.left() = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()]; |
| bound_box.right() = draw_coords->tile_x[device_ctx.rr_nodes[inode].xhigh()] |
| + draw_coords->get_tile_width(); |
| bound_box.bottom() = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()] |
| + draw_coords->get_tile_width() |
| + (1. + device_ctx.rr_nodes[inode].ptc_num()); |
| bound_box.top() = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()] |
| + draw_coords->get_tile_width() |
| + (1. + device_ctx.rr_nodes[inode].ptc_num()); |
| break; |
| case CHANY: |
| bound_box.left() = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()] |
| + draw_coords->get_tile_width() |
| + (1. + device_ctx.rr_nodes[inode].ptc_num()); |
| bound_box.right() = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()] |
| + draw_coords->get_tile_width() |
| + (1. + device_ctx.rr_nodes[inode].ptc_num()); |
| bound_box.bottom() = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()]; |
| bound_box.top() = draw_coords->tile_y[device_ctx.rr_nodes[inode].yhigh()] |
| + draw_coords->get_tile_width(); |
| break; |
| default: |
| // a problem. leave at default value (ie. zeros) |
| break; |
| } |
| |
| return bound_box; |
| } |
| |
| static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, bool buffered, bool configurable) { |
| /* Draws a buffer (triangle) or pass transistor (circle) on the edge * |
| * connecting from to to, depending on the status of buffered. The drawing * |
| * is closest to the from_node, since it reflects the switch type of from. */ |
| |
| if (!buffered) { |
| if (configurable) { /* Draw a circle for a pass transistor */ |
| float xcen = from_x + (to_x - from_x) / 10.; |
| float ycen = from_y + (to_y - from_y) / 10.; |
| const float switch_rad = 0.15; |
| drawarc(xcen, ycen, switch_rad, 0., 360.); |
| } else { |
| //Pass, nothing to draw |
| } |
| } else { /* Buffer */ |
| if (from_x == to_x || from_y == to_y) { |
| //Straight connection |
| draw_triangle_along_line(t_point(from_x, from_y), t_point(to_x, to_y), SB_EDGE_STRAIGHT_ARROW_POSITION); |
| } else { |
| //Turn connection |
| draw_triangle_along_line(t_point(from_x, from_y), t_point(to_x, to_y), SB_EDGE_TURN_ARROW_POSITION); |
| } |
| } |
| } |
| |
| static void draw_rr_pin(int inode, const t_color& color) { |
| /* Draws an IPIN or OPIN rr_node. Note that the pin can appear on more * |
| * than one side of a clb. Also note that this routine can change the * |
| * current color to BLACK. */ |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| //exit early unless zoomed in really far. |
| if (LOD_screen_area_test_square(draw_coords->pin_size, MIN_VISIBLE_AREA) == false) { |
| return; |
| } |
| |
| float xcen, ycen; |
| char str[vtr::bufsize]; |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| int ipin = device_ctx.rr_nodes[inode].ptc_num(); |
| |
| setcolor(color); |
| |
| /* TODO: This is where we can hide fringe physical pins and also identify globals (hide, color, show) */ |
| draw_get_rr_pin_coords(inode, &xcen, &ycen); |
| fillrect(xcen - draw_coords->pin_size, ycen - draw_coords->pin_size, |
| xcen + draw_coords->pin_size, ycen + draw_coords->pin_size); |
| sprintf(str, "%d", ipin); |
| setcolor(BLACK); |
| drawtext(xcen, ycen, str, 2 * draw_coords->pin_size, 2 * draw_coords->pin_size); |
| setcolor(color); |
| } |
| |
| /* Returns the coordinates at which the center of this pin should be drawn. * |
| * inode gives the node number, and iside gives the side of the clb or pad * |
| * the physical pin is on. */ |
| void draw_get_rr_pin_coords(int inode, float* xcen, float* ycen) { |
| auto& device_ctx = g_vpr_ctx.device(); |
| draw_get_rr_pin_coords(&device_ctx.rr_nodes[inode], xcen, ycen); |
| } |
| |
| void draw_get_rr_pin_coords(const t_rr_node* node, float* xcen, float* ycen) { |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| int i, j, k, ipin, pins_per_sub_tile; |
| float offset, xc, yc, step; |
| t_type_ptr type; |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| i = node->xlow(); |
| j = node->ylow(); |
| |
| xc = draw_coords->tile_x[i]; |
| yc = draw_coords->tile_y[j]; |
| |
| ipin = node->ptc_num(); |
| type = device_ctx.grid[i][j].type; |
| pins_per_sub_tile = type->num_pins / type->capacity; |
| k = ipin / pins_per_sub_tile; |
| |
| /* Since pins numbers go across all sub_tiles in a block in order |
| * we can treat as a block box for this step */ |
| |
| /* For each sub_tile we need and extra padding space */ |
| step = (float)(draw_coords->get_tile_width()) / (float)(type->num_pins + type->capacity); |
| offset = (ipin + k + 1) * step; |
| |
| switch (node->side()) { |
| case LEFT: |
| yc += offset; |
| break; |
| |
| case RIGHT: |
| xc += draw_coords->get_tile_width(); |
| yc += offset; |
| break; |
| |
| case BOTTOM: |
| xc += offset; |
| break; |
| |
| case TOP: |
| xc += offset; |
| yc += draw_coords->get_tile_width(); |
| break; |
| |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_get_rr_pin_coords: Unexpected side %s.\n", node->side_string()); |
| break; |
| } |
| |
| *xcen = xc; |
| *ycen = yc; |
| } |
| |
| static void draw_rr_src_sink(int inode, t_color color) { |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| int xlow = device_ctx.rr_nodes[inode].xlow(); |
| int ylow = device_ctx.rr_nodes[inode].ylow(); |
| int xhigh = device_ctx.rr_nodes[inode].xhigh(); |
| int yhigh = device_ctx.rr_nodes[inode].yhigh(); |
| |
| setcolor(color); |
| |
| fillrect(draw_coords->get_tile_width() * xlow, draw_coords->get_tile_height() * ylow, |
| draw_coords->get_tile_width() * xhigh, draw_coords->get_tile_height() * yhigh); |
| } |
| |
| /* Draws the nets in the positions fixed by the router. If draw_net_type is * |
| * ALL_NETS, draw all the nets. If it is HIGHLIGHTED, draw only the nets * |
| * that are not coloured black (useful for drawing over the rr_graph). */ |
| static void drawroute(enum e_draw_net_type draw_net_type) { |
| /* Next free track in each channel segment if routing is GLOBAL */ |
| |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| setlinestyle(SOLID); |
| |
| /* Now draw each net, one by one. */ |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| if (draw_net_type == HIGHLIGHTED && draw_state->net_color[net_id] == BLACK) |
| continue; |
| |
| draw_routed_net(net_id); |
| } /* End for (each net) */ |
| } |
| |
| static void draw_routed_net(ClusterNetId net_id) { |
| auto& route_ctx = g_vpr_ctx.routing(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (cluster_ctx.clb_nlist.net_is_ignored(net_id)) /* Don't draw. */ |
| return; |
| |
| if (route_ctx.trace[net_id].head == nullptr) /* No routing. Skip. (Allows me to draw */ |
| return; /* partially complete routes). */ |
| |
| t_trace* tptr = route_ctx.trace[net_id].head; /* SOURCE to start */ |
| int inode = tptr->index; |
| |
| std::vector<int> rr_nodes_to_draw; |
| rr_nodes_to_draw.push_back(inode); |
| for (;;) { |
| tptr = tptr->next; |
| inode = tptr->index; |
| |
| if (draw_if_net_highlighted(net_id)) { |
| /* If a net has been highlighted, highlight the whole net in * |
| * the same color. */ |
| draw_state->draw_rr_node[inode].color = draw_state->net_color[net_id]; |
| draw_state->draw_rr_node[inode].node_highlighted = true; |
| } else { |
| /* If not highlighted, draw the node in default color. */ |
| draw_state->draw_rr_node[inode].color = DEFAULT_RR_NODE_COLOR; |
| } |
| |
| rr_nodes_to_draw.push_back(inode); |
| |
| if (tptr->iswitch == OPEN) { //End of branch |
| draw_partial_route(rr_nodes_to_draw); |
| rr_nodes_to_draw.clear(); |
| |
| /* Skip the next segment */ |
| tptr = tptr->next; |
| if (tptr == nullptr) |
| break; |
| inode = tptr->index; |
| rr_nodes_to_draw.push_back(inode); |
| } |
| |
| } /* End loop over traceback. */ |
| |
| draw_partial_route(rr_nodes_to_draw); |
| } |
| |
| //Draws the set of rr_nodes specified, using the colors set in draw_state |
| void draw_partial_route(const std::vector<int>& rr_nodes_to_draw) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| static vtr::OffsetMatrix<int> chanx_track; /* [1..device_ctx.grid.width() - 2][0..device_ctx.grid.height() - 2] */ |
| static vtr::OffsetMatrix<int> chany_track; /* [0..device_ctx.grid.width() - 2][1..device_ctx.grid.height() - 2] */ |
| if (draw_state->draw_route_type == GLOBAL) { |
| /* Allocate some temporary storage if it's not already available. */ |
| size_t width = device_ctx.grid.width(); |
| size_t height = device_ctx.grid.height(); |
| if (chanx_track.empty()) { |
| chanx_track = vtr::OffsetMatrix<int>({{{1, width - 1}, {0, height - 1}}}); |
| } |
| |
| if (chany_track.empty()) { |
| chany_track = vtr::OffsetMatrix<int>({{{0, width - 1}, {1, height - 1}}}); |
| } |
| |
| for (size_t i = 1; i < width - 1; i++) |
| for (size_t j = 0; j < height - 1; j++) |
| chanx_track[i][j] = (-1); |
| |
| for (size_t i = 0; i < width - 1; i++) |
| for (size_t j = 1; j < height - 1; j++) |
| chany_track[i][j] = (-1); |
| } |
| |
| for (size_t i = 1; i < rr_nodes_to_draw.size(); ++i) { |
| int inode = rr_nodes_to_draw[i]; |
| auto rr_type = device_ctx.rr_nodes[inode].type(); |
| |
| int prev_node = rr_nodes_to_draw[i - 1]; |
| auto prev_type = device_ctx.rr_nodes[prev_node].type(); |
| |
| int iedge = find_edge(prev_node, inode); |
| auto switch_type = device_ctx.rr_nodes[prev_node].edge_switch(iedge); |
| |
| switch (rr_type) { |
| case OPIN: { |
| draw_rr_pin(inode, draw_state->draw_rr_node[inode].color); |
| break; |
| } |
| case IPIN: { |
| draw_rr_pin(inode, draw_state->draw_rr_node[inode].color); |
| if (device_ctx.rr_nodes[prev_node].type() == OPIN) { |
| draw_pin_to_pin(prev_node, inode); |
| } else { |
| draw_pin_to_chan_edge(inode, prev_node); |
| } |
| break; |
| } |
| case CHANX: { |
| if (draw_state->draw_route_type == GLOBAL) |
| chanx_track[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()]++; |
| |
| int itrack = get_track_num(inode, chanx_track, chany_track); |
| draw_rr_chan(inode, draw_state->draw_rr_node[inode].color); |
| |
| switch (prev_type) { |
| case CHANX: { |
| draw_chanx_to_chanx_edge(prev_node, inode, |
| itrack, switch_type); |
| break; |
| } |
| case CHANY: { |
| int prev_track = get_track_num(prev_node, chanx_track, |
| chany_track); |
| draw_chanx_to_chany_edge(inode, itrack, prev_node, |
| prev_track, FROM_Y_TO_X, switch_type); |
| break; |
| } |
| case OPIN: { |
| draw_pin_to_chan_edge(prev_node, inode); |
| break; |
| } |
| default: { |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "Unexpected connection from an rr_node of type %d to one of type %d.\n", |
| prev_type, rr_type); |
| } |
| } |
| |
| break; |
| } |
| case CHANY: { |
| if (draw_state->draw_route_type == GLOBAL) |
| chany_track[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()]++; |
| |
| int itrack = get_track_num(inode, chanx_track, chany_track); |
| draw_rr_chan(inode, draw_state->draw_rr_node[inode].color); |
| |
| switch (prev_type) { |
| case CHANX: { |
| int prev_track = get_track_num(prev_node, chanx_track, |
| chany_track); |
| draw_chanx_to_chany_edge(prev_node, prev_track, inode, |
| itrack, FROM_X_TO_Y, switch_type); |
| break; |
| } |
| case CHANY: { |
| draw_chany_to_chany_edge(prev_node, inode, |
| itrack, switch_type); |
| break; |
| } |
| case OPIN: { |
| draw_pin_to_chan_edge(prev_node, inode); |
| |
| break; |
| } |
| default: { |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "Unexpected connection from an rr_node of type %d to one of type %d.\n", |
| prev_type, rr_type); |
| } |
| } |
| |
| break; |
| } |
| default: { |
| break; |
| } |
| } |
| } |
| } |
| |
| static int get_track_num(int inode, const vtr::OffsetMatrix<int>& chanx_track, const vtr::OffsetMatrix<int>& chany_track) { |
| /* Returns the track number of this routing resource node. */ |
| |
| int i, j; |
| t_rr_type rr_type; |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| if (get_draw_state_vars()->draw_route_type == DETAILED) |
| return (device_ctx.rr_nodes[inode].ptc_num()); |
| |
| /* GLOBAL route stuff below. */ |
| |
| rr_type = device_ctx.rr_nodes[inode].type(); |
| i = device_ctx.rr_nodes[inode].xlow(); /* NB: Global rr graphs must have only unit */ |
| j = device_ctx.rr_nodes[inode].ylow(); /* length channel segments. */ |
| |
| switch (rr_type) { |
| case CHANX: |
| return (chanx_track[i][j]); |
| |
| case CHANY: |
| return (chany_track[i][j]); |
| |
| default: |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in get_track_num: Unexpected node type %d for node %d.\n", rr_type, inode); |
| return OPEN; |
| } |
| } |
| |
| /* This helper function determines whether a net has been highlighted. The highlighting |
| * could be caused by the user clicking on a routing resource, toggled, or |
| * fan-in/fan-out of a highlighted node. |
| */ |
| static bool draw_if_net_highlighted(ClusterNetId inet) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->net_color[inet] != DEFAULT_RR_NODE_COLOR) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* If an rr_node has been clicked on, it will be highlighted in MAGENTA. |
| * If so, and toggle nets is selected, highlight the whole net in that colour. |
| */ |
| static void highlight_nets(char* message, int hit_node) { |
| t_trace* tptr; |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) { |
| for (tptr = route_ctx.trace[net_id].head; tptr != nullptr; tptr = tptr->next) { |
| if (draw_state->draw_rr_node[tptr->index].color == MAGENTA) { |
| draw_state->net_color[net_id] = draw_state->draw_rr_node[tptr->index].color; |
| if (tptr->index == hit_node) { |
| std::string orig_msg(message); |
| sprintf(message, "%s || Net: %zu (%s)", orig_msg.c_str(), size_t(net_id), |
| cluster_ctx.clb_nlist.net_name(net_id).c_str()); |
| } |
| } else if (draw_state->draw_rr_node[tptr->index].color == WHITE) { |
| // If node is de-selected. |
| draw_state->net_color[net_id] = BLACK; |
| break; |
| } |
| } |
| } |
| update_message(message); |
| } |
| |
| /* If an rr_node has been clicked on, it will be either highlighted in MAGENTA, |
| * or de-highlighted in WHITE. If highlighted, and toggle_rr is selected, highlight |
| * fan_in into the node in blue and fan_out from the node in red. If de-highlighted, |
| * de-highlight its fan_in and fan_out. |
| */ |
| static void draw_highlight_fan_in_fan_out(const std::set<int>& nodes) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| for (auto node : nodes) { |
| /* Highlight the fanout nodes in red. */ |
| for (int iedge = 0, l = device_ctx.rr_nodes[node].num_edges(); iedge < l; iedge++) { |
| int fanout_node = device_ctx.rr_nodes[node].edge_sink_node(iedge); |
| |
| if (draw_state->draw_rr_node[node].color == MAGENTA && draw_state->draw_rr_node[fanout_node].color != MAGENTA) { |
| // If node is highlighted, highlight its fanout |
| draw_state->draw_rr_node[fanout_node].color = DRIVES_IT_COLOR; |
| draw_state->draw_rr_node[fanout_node].node_highlighted = true; |
| } else if (draw_state->draw_rr_node[node].color == WHITE) { |
| // If node is de-highlighted, de-highlight its fanout |
| draw_state->draw_rr_node[fanout_node].color = DEFAULT_RR_NODE_COLOR; |
| draw_state->draw_rr_node[fanout_node].node_highlighted = false; |
| } |
| } |
| |
| /* Highlight the nodes that can fanin to this node in blue. */ |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| for (int iedge = 0, l = device_ctx.rr_nodes[inode].num_edges(); iedge < l; iedge++) { |
| int fanout_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge); |
| if (fanout_node == node) { |
| if (draw_state->draw_rr_node[node].color == MAGENTA && draw_state->draw_rr_node[inode].color != MAGENTA) { |
| // If node is highlighted, highlight its fanin |
| draw_state->draw_rr_node[inode].color = BLUE; |
| draw_state->draw_rr_node[inode].node_highlighted = true; |
| } else if (draw_state->draw_rr_node[node].color == WHITE) { |
| // If node is de-highlighted, de-highlight its fanin |
| draw_state->draw_rr_node[inode].color = DEFAULT_RR_NODE_COLOR; |
| draw_state->draw_rr_node[inode].node_highlighted = false; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* This is a helper function for highlight_rr_nodes(). It determines whether |
| * a routing resource has been clicked on by computing a bounding box for that |
| * and checking if the mouse click hit inside its bounding box. |
| * |
| * It returns the hit RR node's ID (or OPEN if no hit) |
| */ |
| static int draw_check_rr_node_hit(float click_x, float click_y) { |
| int hit_node = OPEN; |
| t_bound_box bound_box; |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case IPIN: |
| case OPIN: { |
| int i = device_ctx.rr_nodes[inode].xlow(); |
| int j = device_ctx.rr_nodes[inode].ylow(); |
| t_type_ptr type = device_ctx.grid[i][j].type; |
| int width_offset = device_ctx.grid[i][j].width_offset; |
| int height_offset = device_ctx.grid[i][j].height_offset; |
| int ipin = device_ctx.rr_nodes[inode].ptc_num(); |
| float xcen, ycen; |
| |
| int iside; |
| for (iside = 0; iside < 4; iside++) { |
| // If pin exists on this side of the block, then get pin coordinates |
| if (type->pinloc[width_offset][height_offset][iside][ipin]) { |
| draw_get_rr_pin_coords(inode, &xcen, &ycen); |
| |
| // Now check if we clicked on this pin |
| if (click_x >= xcen - draw_coords->pin_size && click_x <= xcen + draw_coords->pin_size && click_y >= ycen - draw_coords->pin_size && click_y <= ycen + draw_coords->pin_size) { |
| hit_node = inode; |
| return hit_node; |
| } |
| } |
| } |
| break; |
| } |
| case CHANX: |
| case CHANY: { |
| bound_box = draw_get_rr_chan_bbox(inode); |
| |
| // Check if we clicked on this wire, with 30% |
| // tolerance outside its boundary |
| const float tolerance = 0.3; |
| if (click_x >= bound_box.left() - tolerance && click_x <= bound_box.right() + tolerance && click_y >= bound_box.bottom() - tolerance && click_y <= bound_box.top() + tolerance) { |
| hit_node = inode; |
| return hit_node; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return hit_node; |
| } |
| |
| static std::set<int> draw_expand_non_configurable_rr_nodes(int from_node) { |
| std::set<int> expanded_nodes; |
| draw_expand_non_configurable_rr_nodes_recurr(from_node, expanded_nodes); |
| return expanded_nodes; |
| } |
| |
| static void draw_expand_non_configurable_rr_nodes_recurr(int from_node, std::set<int>& expanded_nodes) { |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| expanded_nodes.insert(from_node); |
| |
| for (int iedge = 0; iedge < device_ctx.rr_nodes[from_node].num_edges(); ++iedge) { |
| bool edge_configurable = device_ctx.rr_nodes[from_node].edge_is_configurable(iedge); |
| int to_node = device_ctx.rr_nodes[from_node].edge_sink_node(iedge); |
| |
| if (!edge_configurable && !expanded_nodes.count(to_node)) { |
| draw_expand_non_configurable_rr_nodes_recurr(to_node, expanded_nodes); |
| } |
| } |
| } |
| |
| /* This routine is called when the routing resource graph is shown, and someone |
| * clicks outside a block. That click might represent a click on a wire -- we call |
| * this routine to determine which wire (if any) was clicked on. If a wire was |
| * clicked upon, we highlight it in Magenta, and its fanout in red. |
| */ |
| static bool highlight_rr_nodes(float x, float y) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| char message[250] = ""; |
| |
| if (draw_state->draw_rr_toggle == DRAW_NO_RR && !draw_state->show_nets) { |
| update_message(draw_state->default_message); |
| drawscreen(); |
| return false; //No rr shown |
| } |
| |
| // Check which rr_node (if any) was clicked on. |
| int hit_node = draw_check_rr_node_hit(x, y); |
| |
| if (hit_node != OPEN) { |
| auto nodes = draw_expand_non_configurable_rr_nodes(hit_node); |
| for (auto node : nodes) { |
| if (draw_state->draw_rr_node[node].color != MAGENTA) { |
| /* If the node hasn't been clicked on before, highlight it |
| * in magenta. |
| */ |
| draw_state->draw_rr_node[node].color = MAGENTA; |
| draw_state->draw_rr_node[node].node_highlighted = true; |
| |
| } else { |
| //Using white color to represent de-highlighting (or |
| //de-selecting) of node. |
| draw_state->draw_rr_node[node].color = WHITE; |
| draw_state->draw_rr_node[node].node_highlighted = false; |
| } |
| |
| //Print info about all nodes to terminal |
| VTR_LOG("%s\n", describe_rr_node(node).c_str()); |
| } |
| |
| //Show info about *only* hit node to graphics |
| std::string info = describe_rr_node(hit_node); |
| |
| sprintf(message, "Selected %s", info.c_str()); |
| rr_highlight_message = message; |
| |
| if (draw_state->draw_rr_toggle != DRAW_NO_RR) { |
| // If rr_graph is shown, highlight the fan-in/fan-outs for |
| // this node. |
| draw_highlight_fan_in_fan_out(nodes); |
| } |
| } else { |
| update_message(draw_state->default_message); |
| rr_highlight_message = ""; |
| drawscreen(); |
| return false; //No hit |
| } |
| |
| if (draw_state->show_nets) { |
| highlight_nets(message, hit_node); |
| } else |
| update_message(message); |
| |
| drawscreen(); |
| return true; //Hit |
| } |
| |
| static void highlight_blocks(float abs_x, float abs_y, t_event_buttonPressed button_info) { |
| /* This routine is called when the user clicks in the graphics area. * |
| * It determines if a clb was clicked on. If one was, it is * |
| * highlighted in green, it's fanin nets and clbs are highlighted in * |
| * blue and it's fanout is highlighted in red. If no clb was * |
| * clicked on (user clicked on white space) any old highlighting is * |
| * removed. Note that even though global nets are not drawn, their * |
| * fanins and fanouts are highlighted when you click on a block * |
| * attached to them. */ |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| char msg[vtr::bufsize]; |
| ClusterBlockId clb_index = EMPTY_BLOCK_ID; |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto& place_ctx = g_vpr_ctx.placement(); |
| |
| /* Control + mouse click to select multiple nets. */ |
| if (!button_info.ctrl_pressed) |
| deselect_all(); |
| |
| //Check if we hit an rr node |
| // Note that we check this before checking for a block, since pins and routing may appear overtop of a multi-width/height block |
| if (highlight_rr_nodes(abs_x, abs_y)) { |
| return; //Selected an rr node |
| } |
| |
| /// determine block /// |
| t_bound_box clb_bbox(0, 0, 0, 0); |
| |
| // iterate over grid x |
| for (size_t i = 0; i < device_ctx.grid.width(); ++i) { |
| if (draw_coords->tile_x[i] > abs_x) { |
| break; // we've gone to far in the x direction |
| } |
| // iterate over grid y |
| for (size_t j = 0; j < device_ctx.grid.height(); ++j) { |
| if (draw_coords->tile_y[j] > abs_y) { |
| break; // we've gone to far in the y direction |
| } |
| // iterate over sub_blocks |
| const t_grid_tile* grid_tile = &device_ctx.grid[i][j]; |
| for (int k = 0; k < grid_tile->type->capacity; ++k) { |
| clb_index = place_ctx.grid_blocks[i][j].blocks[k]; |
| if (clb_index != EMPTY_BLOCK_ID) { |
| clb_bbox = draw_coords->get_absolute_clb_bbox(clb_index, cluster_ctx.clb_nlist.block_type(clb_index)); |
| if (clb_bbox.intersects(abs_x, abs_y)) { |
| break; |
| } else { |
| clb_index = EMPTY_BLOCK_ID; |
| } |
| } |
| } |
| if (clb_index != EMPTY_BLOCK_ID) { |
| break; // we've found something |
| } |
| } |
| if (clb_index != EMPTY_BLOCK_ID) { |
| break; // we've found something |
| } |
| } |
| |
| if (clb_index == EMPTY_BLOCK_ID) { |
| //Nothing found |
| return; |
| } |
| |
| VTR_ASSERT(clb_index != EMPTY_BLOCK_ID); |
| |
| // note: this will clear the selected sub-block if show_blk_internal is 0, |
| // or if it doesn't find anything |
| t_point point_in_clb = t_point(abs_x, abs_y) - clb_bbox.bottom_left(); |
| highlight_sub_block(point_in_clb, clb_index, cluster_ctx.clb_nlist.block_pb(clb_index)); |
| |
| if (get_selected_sub_block_info().has_selection()) { |
| t_pb* selected_subblock = get_selected_sub_block_info().get_selected_pb(); |
| sprintf(msg, "sub-block %s (a \"%s\") selected", |
| selected_subblock->name, selected_subblock->pb_graph_node->pb_type->name); |
| } else { |
| /* Highlight block and fan-in/fan-outs. */ |
| draw_highlight_blocks_color(cluster_ctx.clb_nlist.block_type(clb_index), clb_index); |
| sprintf(msg, "Block #%zu (%s) at (%d, %d) selected.", size_t(clb_index), cluster_ctx.clb_nlist.block_name(clb_index).c_str(), place_ctx.block_locs[clb_index].loc.x, place_ctx.block_locs[clb_index].loc.y); |
| } |
| |
| update_message(msg); |
| |
| drawscreen(); /* Need to erase screen. */ |
| } |
| |
| static void act_on_mouse_over(float mouse_x, float mouse_y) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->draw_rr_toggle != DRAW_NO_RR) { |
| int hit_node = draw_check_rr_node_hit(mouse_x, mouse_y); |
| |
| if (hit_node != OPEN) { |
| //Update message |
| |
| std::string info = describe_rr_node(hit_node); |
| std::string msg = vtr::string_fmt("Moused over %s", info.c_str()); |
| update_message(msg.c_str()); |
| } else { |
| //No rr node moused over, reset message |
| if (!rr_highlight_message.empty()) { |
| update_message(rr_highlight_message.c_str()); |
| } else { |
| update_message(draw_state->default_message); |
| } |
| } |
| } |
| } |
| |
| #if defined(X11) && !defined(__MINGW32__) |
| static void act_on_key_press(char /*key_pressed*/, int keysym) { |
| //VTR_LOG("Key press %c (%d)\n", key_pressed, keysym); |
| switch (keysym) { |
| case XK_Shift_L: { |
| float zoom_factor = get_zoom_factor(); |
| zoom_factor += (zoom_factor - 1.); |
| zoom_factor = std::min(8.f, zoom_factor); //Clip maximum zoom factor |
| VTR_LOG("Increasing graphics zoom factor to %f\n", zoom_factor); |
| set_zoom_factor(zoom_factor); |
| break; |
| } |
| case XK_Control_L: { |
| float zoom_factor = get_zoom_factor(); |
| zoom_factor -= (zoom_factor - 1.) / 2; |
| zoom_factor = std::max(1.0001f, zoom_factor); //Clip minimum zoom factor |
| VTR_LOG("Decreasing graphics zoom factor to %f\n", zoom_factor); |
| set_zoom_factor(zoom_factor); |
| break; |
| } |
| default: |
| break; //Unrecognized |
| } |
| } |
| #else |
| static void act_on_key_press(char /*key_pressed*/, int /*keysym*/) { |
| //Nothing to do |
| } |
| #endif |
| |
| static void draw_highlight_blocks_color(t_type_ptr type, ClusterBlockId blk_id) { |
| int k, iclass; |
| ClusterBlockId fanblk; |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| for (k = 0; k < type->num_pins; k++) { /* Each pin on a CLB */ |
| ClusterNetId net_id = cluster_ctx.clb_nlist.block_net(blk_id, k); |
| |
| if (net_id == ClusterNetId::INVALID()) |
| continue; |
| |
| iclass = type->pin_class[k]; |
| |
| if (type->class_inf[iclass].type == DRIVER) { /* Fanout */ |
| if (draw_state->block_color[blk_id] == SELECTED_COLOR) { |
| /* If block already highlighted, de-highlight the fanout. (the deselect case)*/ |
| draw_state->net_color[net_id] = BLACK; |
| for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { |
| fanblk = cluster_ctx.clb_nlist.pin_block(pin_id); |
| draw_reset_blk_color(fanblk); |
| } |
| } else { |
| /* Highlight the fanout */ |
| draw_state->net_color[net_id] = DRIVES_IT_COLOR; |
| for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { |
| fanblk = cluster_ctx.clb_nlist.pin_block(pin_id); |
| draw_state->block_color[fanblk] = DRIVES_IT_COLOR; |
| } |
| } |
| } else { /* This net is fanin to the block. */ |
| if (draw_state->block_color[blk_id] == SELECTED_COLOR) { |
| /* If block already highlighted, de-highlight the fanin. (the deselect case)*/ |
| draw_state->net_color[net_id] = BLACK; |
| fanblk = cluster_ctx.clb_nlist.net_driver_block(net_id); /* DRIVER to net */ |
| draw_reset_blk_color(fanblk); |
| } else { |
| /* Highlight the fanin */ |
| draw_state->net_color[net_id] = DRIVEN_BY_IT_COLOR; |
| fanblk = cluster_ctx.clb_nlist.net_driver_block(net_id); /* DRIVER to net */ |
| draw_state->block_color[fanblk] = DRIVEN_BY_IT_COLOR; |
| } |
| } |
| } |
| |
| if (draw_state->block_color[blk_id] == SELECTED_COLOR) { |
| /* If block already highlighted, de-highlight the selected block. */ |
| draw_reset_blk_color(blk_id); |
| } else { |
| /* Highlight the selected block. */ |
| draw_state->block_color[blk_id] = SELECTED_COLOR; |
| } |
| } |
| |
| static void deselect_all() { |
| // Sets the color of all clbs, nets and rr_nodes to the default. |
| // as well as clearing the highlighed sub-block |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| /* Create some colour highlighting */ |
| for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { |
| draw_reset_blk_color(blk_id); |
| } |
| |
| for (auto net_id : cluster_ctx.clb_nlist.nets()) |
| draw_state->net_color[net_id] = BLACK; |
| |
| for (size_t i = 0; i < device_ctx.rr_nodes.size(); i++) { |
| draw_state->draw_rr_node[i].color = DEFAULT_RR_NODE_COLOR; |
| draw_state->draw_rr_node[i].node_highlighted = false; |
| } |
| |
| get_selected_sub_block_info().clear(); |
| } |
| |
| static void draw_reset_blk_color(ClusterBlockId blk_id) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| draw_state->block_color[blk_id] = get_block_type_color(cluster_ctx.clb_nlist.block_type(blk_id)); |
| } |
| |
| /** |
| * Draws a small triange, at a position along a line from 'start' to 'end'. |
| * |
| * 'relative_position' [0., 1] defines the triangles position relative to 'start'. |
| * |
| * A 'relative_position' of 0. draws the triangle centered at 'start'. |
| * A 'relative_position' of 1. draws the triangle centered at 'end'. |
| * Fractional values draw the triangle along the line |
| */ |
| void draw_triangle_along_line(t_point start, t_point end, float relative_position, float arrow_size) { |
| VTR_ASSERT(relative_position >= 0. && relative_position <= 1.); |
| float xdelta = end.x - start.x; |
| float ydelta = end.y - start.y; |
| |
| float xtri = start.x + xdelta * relative_position; |
| float ytri = start.y + ydelta * relative_position; |
| |
| draw_triangle_along_line(xtri, ytri, start.x, end.x, start.y, end.y, arrow_size); |
| } |
| |
| /* Draws a trangle with it's center at loc, and of length & width |
| * arrow_size, rotated such that it points in the direction |
| * of the directed line segment start -> end. |
| */ |
| void draw_triangle_along_line(t_point loc, t_point start, t_point end, float arrow_size) { |
| draw_triangle_along_line(loc.x, loc.y, start.x, end.x, start.y, end.y, arrow_size); |
| } |
| |
| /** |
| * Draws a trangle with it's center at (xend, yend), and of length & width |
| * arrow_size, rotated such that it points in the direction |
| * of the directed line segment (x1, y1) -> (x2, y2). |
| * |
| * Note that the parameters are in a strange order |
| */ |
| void draw_triangle_along_line(float xend, float yend, float x1, float x2, float y1, float y2, float arrow_size) { |
| float switch_rad = arrow_size / 2; |
| float xdelta, ydelta; |
| float magnitude; |
| float xunit, yunit; |
| float xbaseline, ybaseline; |
| t_point poly[3]; |
| |
| xdelta = x2 - x1; |
| ydelta = y2 - y1; |
| magnitude = sqrt(xdelta * xdelta + ydelta * ydelta); |
| |
| xunit = xdelta / magnitude; |
| yunit = ydelta / magnitude; |
| |
| poly[0].x = xend + xunit * switch_rad; |
| poly[0].y = yend + yunit * switch_rad; |
| xbaseline = xend - xunit * switch_rad; |
| ybaseline = yend - yunit * switch_rad; |
| poly[1].x = xbaseline + yunit * switch_rad; |
| poly[1].y = ybaseline - xunit * switch_rad; |
| poly[2].x = xbaseline - yunit * switch_rad; |
| poly[2].y = ybaseline + xunit * switch_rad; |
| |
| fillpoly(poly, 3); |
| } |
| |
| static inline bool LOD_screen_area_test_square(float width, float screen_area_threshold) { |
| //Since world coordinates get clipped when converted to screen (at high zoom levels), |
| //we can not pick an arbitrary world root coordinate for the rectange we want to test, |
| //as clipping could cause it's area to go to zero when we convert from world to screen |
| //coordinates. |
| // |
| //Instead we specify an on-screen location for the rectangle we plan to test |
| t_point lower_left = scrn_to_world(t_point(0., 0.)); //Pick one corner of the screen |
| |
| //Offset by the width |
| t_point upper_right = lower_left; |
| upper_right.offset(width, width); |
| |
| t_bound_box world_rect = t_bound_box(lower_left, upper_right); |
| |
| return LOD_screen_area_test(world_rect, screen_area_threshold); |
| } |
| |
| static inline bool default_triangle_LOD_screen_area_test() { |
| return triangle_LOD_screen_area_test(DEFAULT_ARROW_SIZE); |
| } |
| |
| static inline bool triangle_LOD_screen_area_test(float arrow_size) { |
| return LOD_screen_area_test_square(arrow_size * 0.66, MIN_VISIBLE_AREA); |
| } |
| |
| static void draw_pin_to_chan_edge(int pin_node, int chan_node) { |
| /* This routine draws an edge from the pin_node to the chan_node (CHANX or * |
| * CHANY). The connection is made to the nearest end of the track instead * |
| * of perpundicular to the track to symbolize a single-drive connection. */ |
| |
| /* TODO: Fix this for global routing, currently for detailed only */ |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| const t_rr_node& pin_rr = device_ctx.rr_nodes[pin_node]; |
| const t_rr_node& chan_rr = device_ctx.rr_nodes[chan_node]; |
| |
| const t_grid_tile& grid_tile = device_ctx.grid[pin_rr.xlow()][pin_rr.ylow()]; |
| t_type_ptr grid_type = grid_tile.type; |
| |
| VTR_ASSERT_MSG(grid_type->pinloc[grid_tile.width_offset][grid_tile.height_offset][pin_rr.side()][pin_rr.pin_num()], |
| "Pin coordinates should match block type pin locations"); |
| |
| float draw_pin_offset; |
| if (pin_rr.side() == TOP || pin_rr.side() == RIGHT) { |
| draw_pin_offset = draw_coords->pin_size; |
| } else { |
| VTR_ASSERT(pin_rr.side() == BOTTOM || pin_rr.side() == LEFT); |
| draw_pin_offset = -draw_coords->pin_size; |
| } |
| |
| float x1 = 0, y1 = 0; |
| draw_get_rr_pin_coords(pin_node, &x1, &y1); |
| |
| t_bound_box chan_bbox = draw_get_rr_chan_bbox(chan_node); |
| |
| float x2 = 0, y2 = 0; |
| switch (chan_rr.type()) { |
| case CHANX: { |
| y1 += draw_pin_offset; |
| y2 = chan_bbox.bottom(); |
| x2 = x1; |
| if (is_opin(pin_rr.pin_num(), grid_type)) { |
| if (chan_rr.direction() == INC_DIRECTION) { |
| x2 = chan_bbox.left(); |
| } else if (chan_rr.direction() == DEC_DIRECTION) { |
| x2 = chan_bbox.right(); |
| } |
| } |
| break; |
| } |
| case CHANY: { |
| x1 += draw_pin_offset; |
| x2 = chan_bbox.left(); |
| y2 = y1; |
| if (is_opin(pin_rr.pin_num(), grid_type)) { |
| if (chan_rr.direction() == INC_DIRECTION) { |
| y2 = chan_bbox.bottom(); |
| } else if (chan_rr.direction() == DEC_DIRECTION) { |
| y2 = chan_bbox.top(); |
| } |
| } |
| break; |
| } |
| default: { |
| vpr_throw(VPR_ERROR_OTHER, __FILE__, __LINE__, |
| "in draw_pin_to_chan_edge: Invalid channel node %d.\n", chan_node); |
| } |
| } |
| |
| drawline(x1, y1, x2, y2); |
| |
| //don't draw the ex, or triangle unless zoomed in really far |
| if (chan_rr.direction() == BI_DIRECTION || !is_opin(pin_rr.pin_num(), grid_type)) { |
| if (LOD_screen_area_test_square(draw_coords->pin_size * 1.3, MIN_VISIBLE_AREA) == true) { |
| draw_x(x2, y2, 0.7 * draw_coords->pin_size); |
| } |
| } else { |
| if (default_triangle_LOD_screen_area_test() == true) { |
| float xend = x2 + (x1 - x2) / 10.; |
| float yend = y2 + (y1 - y2) / 10.; |
| draw_triangle_along_line(xend, yend, x1, x2, y1, y2); |
| } |
| } |
| } |
| |
| static void draw_pin_to_pin(int opin_node, int ipin_node) { |
| /* This routine draws an edge from the opin rr node to the ipin rr node */ |
| auto& device_ctx = g_vpr_ctx.device(); |
| VTR_ASSERT(device_ctx.rr_nodes[opin_node].type() == OPIN); |
| VTR_ASSERT(device_ctx.rr_nodes[ipin_node].type() == IPIN); |
| |
| float x1 = 0, y1 = 0; |
| draw_get_rr_pin_coords(opin_node, &x1, &y1); |
| |
| float x2 = 0, y2 = 0; |
| draw_get_rr_pin_coords(ipin_node, &x2, &y2); |
| |
| drawline(x1, y1, x2, y2); |
| |
| if (default_triangle_LOD_screen_area_test() == true) { |
| float xend = x2 + (x1 - x2) / 10.; |
| float yend = y2 + (y1 - y2) / 10.; |
| draw_triangle_along_line(xend, yend, x1, x2, y1, y2); |
| } |
| } |
| |
| static inline void draw_mux_with_size(t_point origin, e_side orientation, float height, int size) { |
| setcolor(YELLOW); |
| auto bounds = draw_mux(origin, orientation, height); |
| |
| setcolor(BLACK); |
| drawtext_in(bounds, std::to_string(size)); |
| } |
| |
| //Draws a mux |
| static inline t_bound_box draw_mux(t_point origin, e_side orientation, float height) { |
| return draw_mux(origin, orientation, height, 0.4 * height, 0.6); |
| } |
| |
| //Draws a mux, height/width define the bounding box, scale [0.,1.] controls the slope of the muxes sides |
| static inline t_bound_box draw_mux(t_point origin, e_side orientation, float height, float width, float scale) { |
| std::array<t_point, 4> mux_polygon; |
| |
| switch (orientation) { |
| case TOP: |
| //Clock-wise from bottom left |
| mux_polygon[0] = t_point(origin.x - height / 2, origin.y - width / 2); |
| mux_polygon[1] = t_point(origin.x - (scale * height) / 2, origin.y + width / 2); |
| mux_polygon[2] = t_point(origin.x + (scale * height) / 2, origin.y + width / 2); |
| mux_polygon[3] = t_point(origin.x + height / 2, origin.y - width / 2); |
| break; |
| case BOTTOM: |
| //Clock-wise from bottom left |
| mux_polygon[0] = t_point(origin.x - (scale * height) / 2, origin.y - width / 2); |
| mux_polygon[1] = t_point(origin.x - height / 2, origin.y + width / 2); |
| mux_polygon[2] = t_point(origin.x + height / 2, origin.y + width / 2); |
| mux_polygon[3] = t_point(origin.x + (scale * height) / 2, origin.y - width / 2); |
| break; |
| case LEFT: |
| //Clock-wise from bottom left |
| mux_polygon[0] = t_point(origin.x - width / 2, origin.y - (scale * height) / 2); |
| mux_polygon[1] = t_point(origin.x - width / 2, origin.y + (scale * height) / 2); |
| mux_polygon[2] = t_point(origin.x + width / 2, origin.y + height / 2); |
| mux_polygon[3] = t_point(origin.x + width / 2, origin.y - height / 2); |
| break; |
| case RIGHT: |
| //Clock-wise from bottom left |
| mux_polygon[0] = t_point(origin.x - width / 2, origin.y - height / 2); |
| mux_polygon[1] = t_point(origin.x - width / 2, origin.y + height / 2); |
| mux_polygon[2] = t_point(origin.x + width / 2, origin.y + (scale * height) / 2); |
| mux_polygon[3] = t_point(origin.x + width / 2, origin.y - (scale * height) / 2); |
| break; |
| |
| default: |
| VTR_ASSERT_MSG(false, "Unrecognized orientation"); |
| } |
| |
| fillpoly(mux_polygon.data(), mux_polygon.size()); |
| |
| t_point min = mux_polygon[0]; |
| t_point max = mux_polygon[0]; |
| for (const auto& point : mux_polygon) { |
| min.x = std::min(min.x, point.x); |
| min.y = std::min(min.y, point.y); |
| max.x = std::max(max.x, point.x); |
| max.y = std::max(max.y, point.y); |
| } |
| |
| return t_bound_box(min, max); |
| } |
| |
| t_point tnode_draw_coord(tatum::NodeId node) { |
| auto& atom_ctx = g_vpr_ctx.atom(); |
| |
| AtomPinId pin = atom_ctx.lookup.tnode_atom_pin(node); |
| return atom_pin_draw_coord(pin); |
| } |
| |
| t_point atom_pin_draw_coord(AtomPinId pin) { |
| auto& atom_ctx = g_vpr_ctx.atom(); |
| |
| AtomBlockId blk = atom_ctx.nlist.pin_block(pin); |
| ClusterBlockId clb_index = atom_ctx.lookup.atom_clb(blk); |
| const t_pb_graph_node* pg_gnode = atom_ctx.lookup.atom_pb_graph_node(blk); |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| t_bound_box pb_bbox = draw_coords->get_absolute_pb_bbox(clb_index, pg_gnode); |
| |
| //We place each atom pin inside it's pb bounding box |
| //and distribute the pins along it's vertical centre line |
| const float FRACTION_USABLE_WIDTH = 0.8; |
| float width = pb_bbox.get_width(); |
| float usable_width = width * FRACTION_USABLE_WIDTH; |
| float x_offset = pb_bbox.left() + width * (1 - FRACTION_USABLE_WIDTH) / 2; |
| |
| int pin_index, pin_total; |
| find_pin_index_at_model_scope(pin, blk, &pin_index, &pin_total); |
| |
| const t_point point = { |
| x_offset + usable_width * pin_index / ((float)pin_total), |
| pb_bbox.get_ycenter()}; |
| |
| return point; |
| } |
| |
| static void draw_crit_path() { |
| tatum::TimingPathCollector path_collector; |
| |
| t_draw_state* draw_state = get_draw_state_vars(); |
| auto& timing_ctx = g_vpr_ctx.timing(); |
| |
| if (draw_state->show_crit_path == DRAW_NO_CRIT_PATH) { |
| return; |
| } |
| |
| if (!draw_state->setup_timing_info) { |
| return; //No timing to draw |
| } |
| |
| //Get the worst timing path |
| auto paths = path_collector.collect_worst_setup_timing_paths(*timing_ctx.graph, *(draw_state->setup_timing_info->setup_analyzer()), 1); |
| tatum::TimingPath path = paths[0]; |
| |
| //Walk through the timing path drawing each edge |
| tatum::NodeId prev_node; |
| float prev_arr_time = std::numeric_limits<float>::quiet_NaN(); |
| int i = 0; |
| for (tatum::TimingPathElem elem : path.data_arrival_path().elements()) { |
| tatum::NodeId node = elem.node(); |
| float arr_time = elem.tag().time(); |
| |
| if (prev_node) { |
| //We draw each 'edge' in a different color, this allows users to identify the stages and |
| //any routing which corresponds to the edge |
| // |
| //We pick colors from the kelly max-contrast list, for long paths there may be repeats |
| t_color color = kelly_max_contrast_colors[i++ % kelly_max_contrast_colors.size()]; |
| |
| float delay = arr_time - prev_arr_time; |
| if (draw_state->show_crit_path == DRAW_CRIT_PATH_FLYLINES || draw_state->show_crit_path == DRAW_CRIT_PATH_FLYLINES_DELAYS) { |
| setcolor(color); |
| setlinestyle(SOLID); |
| draw_flyline_timing_edge(tnode_draw_coord(prev_node), tnode_draw_coord(node), delay); |
| } else { |
| VTR_ASSERT(draw_state->show_crit_path != DRAW_NO_CRIT_PATH); |
| |
| //Draw the routed version of the timing edge |
| draw_routed_timing_edge(prev_node, node, delay, color); |
| } |
| } |
| prev_node = node; |
| prev_arr_time = arr_time; |
| } |
| } |
| |
| static void draw_flyline_timing_edge(t_point start, t_point end, float incr_delay) { |
| drawline(start, end); |
| draw_triangle_along_line(start, end, 0.95, 40 * DEFAULT_ARROW_SIZE); |
| draw_triangle_along_line(start, end, 0.05, 40 * DEFAULT_ARROW_SIZE); |
| |
| bool draw_delays = (get_draw_state_vars()->show_crit_path == DRAW_CRIT_PATH_FLYLINES_DELAYS |
| || get_draw_state_vars()->show_crit_path == DRAW_CRIT_PATH_ROUTING_DELAYS); |
| if (draw_delays) { |
| //Determine the strict bounding box based on the lines start/end |
| float min_x = std::min(start.x, end.x); |
| float max_x = std::max(start.x, end.x); |
| float min_y = std::min(start.y, end.y); |
| float max_y = std::max(start.y, end.y); |
| |
| //If we have a nearly horizontal/vertical line the bbox is too |
| //small to draw the text, so widen it by a tile (i.e. CLB) width |
| float tile_width = get_draw_coords_vars()->get_tile_width(); |
| if (max_x - min_x < tile_width) { |
| max_x += tile_width / 2; |
| min_x -= tile_width / 2; |
| } |
| if (max_y - min_y < tile_width) { |
| max_y += tile_width / 2; |
| min_y -= tile_width / 2; |
| } |
| |
| //TODO: draw the delays nicer |
| // * rotate to match edge |
| // * offset from line |
| // * track visible in window |
| t_bound_box text_bbox(min_x, min_y, max_x, max_y); |
| |
| std::stringstream ss; |
| ss.precision(3); |
| ss << 1e9 * incr_delay; //In nanoseconds |
| std::string incr_delay_str = ss.str(); |
| |
| drawtext_in(text_bbox, incr_delay_str.c_str()); |
| } |
| } |
| |
| static void draw_routed_timing_edge(tatum::NodeId start_tnode, tatum::NodeId end_tnode, float incr_delay, t_color color) { |
| draw_routed_timing_edge_connection(start_tnode, end_tnode, color); |
| |
| setlinestyle(DASHED); |
| setlinewidth(3); |
| setcolor(color); |
| |
| draw_flyline_timing_edge(tnode_draw_coord(start_tnode), tnode_draw_coord(end_tnode), incr_delay); |
| |
| setlinewidth(0); |
| setlinestyle(SOLID); |
| } |
| |
| //Collect all the drawing locations associated with the timing edge between start and end |
| static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::NodeId sink_tnode, t_color color) { |
| auto& atom_ctx = g_vpr_ctx.atom(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto& timing_ctx = g_vpr_ctx.timing(); |
| |
| AtomPinId atom_src_pin = atom_ctx.lookup.tnode_atom_pin(src_tnode); |
| AtomPinId atom_sink_pin = atom_ctx.lookup.tnode_atom_pin(sink_tnode); |
| |
| std::vector<t_point> points; |
| points.push_back(atom_pin_draw_coord(atom_src_pin)); |
| |
| tatum::EdgeId tedge = timing_ctx.graph->find_edge(src_tnode, sink_tnode); |
| tatum::EdgeType edge_type = timing_ctx.graph->edge_type(tedge); |
| |
| ClusterNetId net_id = ClusterNetId::INVALID(); |
| |
| //We currently only trace interconnect edges in detail, and treat all others |
| //as flylines |
| if (edge_type == tatum::EdgeType::INTERCONNECT) { |
| //All atom pins are implemented inside CLBs, so next hop is to the top-level CLB pins |
| |
| //TODO: most of this code is highly similar to code in PostClusterDelayCalculator, refactor |
| // into a common method for walking the clustered netlist, this would also (potentially) |
| // allow us to grab the component delays |
| AtomBlockId atom_src_block = atom_ctx.nlist.pin_block(atom_src_pin); |
| AtomBlockId atom_sink_block = atom_ctx.nlist.pin_block(atom_sink_pin); |
| |
| ClusterBlockId clb_src_block = atom_ctx.lookup.atom_clb(atom_src_block); |
| VTR_ASSERT(clb_src_block != ClusterBlockId::INVALID()); |
| ClusterBlockId clb_sink_block = atom_ctx.lookup.atom_clb(atom_sink_block); |
| VTR_ASSERT(clb_sink_block != ClusterBlockId::INVALID()); |
| |
| const t_pb_graph_pin* sink_gpin = atom_ctx.lookup.atom_pin_pb_graph_pin(atom_sink_pin); |
| VTR_ASSERT(sink_gpin); |
| |
| int sink_pb_route_id = sink_gpin->pin_count_in_cluster; |
| |
| int sink_block_pin_index = -1; |
| int sink_net_pin_index = -1; |
| |
| std::tie(net_id, sink_block_pin_index, sink_net_pin_index) = find_pb_route_clb_input_net_pin(clb_sink_block, sink_pb_route_id); |
| if (net_id != ClusterNetId::INVALID() && sink_block_pin_index != -1 && sink_net_pin_index != -1) { |
| //Connection leaves the CLB |
| //Now that we have the CLB source and sink pins, we need to grab all the points on the routing connecting the pins |
| VTR_ASSERT(cluster_ctx.clb_nlist.net_driver_block(net_id) == clb_src_block); |
| |
| auto routed_rr_nodes = trace_routed_connection_rr_nodes(net_id, 0, sink_net_pin_index); |
| |
| //Mark all the nodes highlighted |
| t_draw_state* draw_state = get_draw_state_vars(); |
| for (int inode : routed_rr_nodes) { |
| draw_state->draw_rr_node[inode].color = color; |
| } |
| |
| draw_partial_route(routed_rr_nodes); |
| } else { |
| //Connection entirely within the CLB, we don't draw the internal routing so treat it as a fly-line |
| VTR_ASSERT(clb_src_block == clb_sink_block); |
| } |
| } |
| |
| points.push_back(atom_pin_draw_coord(atom_sink_pin)); |
| } |
| |
| //Returns the set of rr nodes which connect driver to sink |
| static std::vector<int> trace_routed_connection_rr_nodes(const ClusterNetId net_id, const int driver_pin, const int sink_pin) { |
| auto& route_ctx = g_vpr_ctx.routing(); |
| |
| bool allocated_route_tree_structs = alloc_route_tree_timing_structs(true); //Needed for traceback_to_route_tree |
| |
| //Conver the traceback into an easily search-able |
| t_rt_node* rt_root = traceback_to_route_tree(net_id); |
| |
| VTR_ASSERT(rt_root && rt_root->inode == route_ctx.net_rr_terminals[net_id][driver_pin]); |
| |
| int sink_rr_node = route_ctx.net_rr_terminals[net_id][sink_pin]; |
| |
| std::vector<int> rr_nodes_on_path; |
| |
| //Collect the rr nodes |
| trace_routed_connection_rr_nodes_recurr(rt_root, sink_rr_node, rr_nodes_on_path); |
| |
| //Traced from sink to source, but we want to draw from source to sink |
| std::reverse(rr_nodes_on_path.begin(), rr_nodes_on_path.end()); |
| |
| if (allocated_route_tree_structs) { |
| clear_rr_node_to_rt_node(); //Clean-up |
| free_route_tree_timing_structs(); |
| //See discussion in route_tree_timing.cpp on clear_rr_node_to_rt_node usage. |
| } |
| return rr_nodes_on_path; |
| } |
| |
| //Helper function for trace_routed_connection_rr_nodes |
| //Adds the rr nodes linking rt_node to sink_rr_node to rr_nodes_on_path |
| //Returns true if rt_node is on the path |
| bool trace_routed_connection_rr_nodes_recurr(const t_rt_node* rt_node, int sink_rr_node, std::vector<int>& rr_nodes_on_path) { |
| //DFS from the current rt_node to the sink_rr_node, when the sink is found trace back the used rr nodes |
| |
| if (rt_node->inode == sink_rr_node) { |
| rr_nodes_on_path.push_back(sink_rr_node); |
| return true; |
| } |
| |
| for (t_linked_rt_edge* edge = rt_node->u.child_list; edge != nullptr; edge = edge->next) { |
| t_rt_node* child_rt_node = edge->child; |
| VTR_ASSERT(child_rt_node); |
| |
| bool on_path_to_sink = trace_routed_connection_rr_nodes_recurr(child_rt_node, sink_rr_node, rr_nodes_on_path); |
| |
| if (on_path_to_sink) { |
| rr_nodes_on_path.push_back(rt_node->inode); |
| return true; |
| } |
| } |
| |
| return false; //Not on path to sink |
| } |
| |
| //Find the edge between two rr nodes |
| static int find_edge(int prev_inode, int inode) { |
| auto& device_ctx = g_vpr_ctx.device(); |
| for (int iedge = 0; iedge < device_ctx.rr_nodes[prev_inode].num_edges(); ++iedge) { |
| if (device_ctx.rr_nodes[prev_inode].edge_sink_node(iedge) == inode) { |
| return iedge; |
| } |
| } |
| VTR_ASSERT(false); |
| return OPEN; |
| } |
| |
| t_color to_t_color(vtr::Color<float> color) { |
| return t_color(color.r * 255, color.g * 255, color.b * 255); |
| } |
| |
| static void draw_color_map_legend(const vtr::ColorMap& cmap) { |
| constexpr float LEGEND_WIDTH_FAC = 0.075; |
| constexpr float LEGEND_VERT_OFFSET_FAC = 0.05; |
| constexpr float TEXT_OFFSET = 10; |
| constexpr size_t NUM_COLOR_POINTS = 1000; |
| |
| set_coordinate_system(GL_SCREEN); |
| t_bound_box visible_screen = get_visible_screen(); |
| |
| float screen_width = visible_screen.get_width(); |
| float vert_offset = visible_screen.get_height() * LEGEND_VERT_OFFSET_FAC; |
| float legend_width = std::min<int>(LEGEND_WIDTH_FAC * screen_width, 100); |
| |
| t_bound_box legend(visible_screen.left(), visible_screen.bottom() + vert_offset, visible_screen.left() + legend_width, visible_screen.top() - vert_offset); |
| |
| float range = cmap.max() - cmap.min(); |
| float height_incr = legend.get_height() / float(NUM_COLOR_POINTS); |
| for (size_t i = 0; i < NUM_COLOR_POINTS; ++i) { |
| float val = cmap.min() + (float(i) / NUM_COLOR_POINTS) * range; |
| t_color color = to_t_color(cmap.color(val)); |
| |
| t_bound_box cbox = {legend.left(), legend.bottom() + i * height_incr, |
| legend.right(), legend.bottom() + (i + 1) * height_incr}; |
| setcolor(color); |
| fillrect(cbox); |
| } |
| |
| setcolor(BLACK); |
| |
| //Min mark |
| std::string str = vtr::string_fmt("%.3g", cmap.min()); |
| drawtext(legend.get_xcenter(), legend.bottom() + TEXT_OFFSET, str.c_str()); |
| |
| //Mid marker |
| str = vtr::string_fmt("%.3g", cmap.min() + (cmap.range() / 2.)); |
| drawtext(legend.get_xcenter(), legend.get_ycenter(), str.c_str()); |
| |
| //Max marker |
| str = vtr::string_fmt("%.3g", cmap.max()); |
| drawtext(legend.get_xcenter(), legend.top() - TEXT_OFFSET, str.c_str()); |
| |
| setcolor(BLACK); |
| drawrect(legend); |
| |
| set_coordinate_system(GL_WORLD); |
| } |
| |
| t_color get_block_type_color(t_type_ptr type) { |
| //Wrap around if there are too many blocks |
| // This ensures we support an arbitrary number of types, |
| // although the colours may repeat |
| t_color color = block_type_colors[type->index % block_type_colors.size()]; |
| |
| return color; |
| } |
| |
| //Lightens a color's luminance [0, 1] by an aboslute 'amount' |
| t_color lighten_color(t_color color, float amount) { |
| constexpr double MAX_LUMINANCE = 0.95; //Clip luminance so it doesn't go full white |
| auto hsl = color2hsl(color); |
| |
| hsl.l = std::max(0., std::min(MAX_LUMINANCE, hsl.l + amount)); |
| |
| return hsl2color(hsl); |
| } |
| |
| static void draw_block_pin_util() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| if (draw_state->show_blk_pin_util == DRAW_NO_BLOCK_PIN_UTIL) { |
| return; |
| } |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| |
| std::map<t_type_ptr, size_t> total_input_pins; |
| std::map<t_type_ptr, size_t> total_output_pins; |
| for (int itype = 0; itype < device_ctx.num_block_types; ++itype) { |
| t_type_ptr type = &device_ctx.block_types[itype]; |
| if (is_empty_type(type)) continue; |
| |
| t_pb_type* pb_type = type->pb_type; |
| |
| total_input_pins[type] = pb_type->num_input_pins + pb_type->num_clock_pins; |
| total_output_pins[type] = pb_type->num_output_pins; |
| } |
| |
| auto blks = cluster_ctx.clb_nlist.blocks(); |
| vtr::vector<ClusterBlockId, float> pin_util(blks.size()); |
| for (auto blk : blks) { |
| auto type = cluster_ctx.clb_nlist.block_type(blk); |
| |
| if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_TOTAL) { |
| pin_util[blk] = cluster_ctx.clb_nlist.block_pins(blk).size() / float(total_input_pins[type] + total_output_pins[type]); |
| } else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_INPUTS) { |
| pin_util[blk] = (cluster_ctx.clb_nlist.block_input_pins(blk).size() + cluster_ctx.clb_nlist.block_clock_pins(blk).size()) / float(total_input_pins[type]); |
| } else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_OUTPUTS) { |
| pin_util[blk] = (cluster_ctx.clb_nlist.block_output_pins(blk).size()) / float(total_output_pins[type]); |
| } else { |
| VTR_ASSERT(false); |
| } |
| } |
| |
| std::unique_ptr<vtr::ColorMap> cmap = std::make_unique<vtr::PlasmaColorMap>(0., 1.); |
| |
| for (auto blk : blks) { |
| t_color color = to_t_color(cmap->color(pin_util[blk])); |
| draw_state->block_color[blk] = color; |
| } |
| |
| draw_state->color_map = std::move(cmap); |
| |
| if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_TOTAL) { |
| update_message("Block Total Pin Utilization"); |
| } else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_INPUTS) { |
| update_message("Block Input Pin Utilization"); |
| |
| } else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_OUTPUTS) { |
| update_message("Block Output Pin Utilization"); |
| } else { |
| VTR_ASSERT(false); |
| } |
| } |
| |
| static void draw_reset_blk_colors() { |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| auto blks = cluster_ctx.clb_nlist.blocks(); |
| for (auto blk : blks) { |
| draw_reset_blk_color(blk); |
| } |
| } |
| |
| static void draw_routing_util() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| if (draw_state->show_routing_util == DRAW_NO_ROUTING_UTIL) { |
| return; |
| } |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| auto chanx_usage = calculate_routing_usage(CHANX); |
| auto chany_usage = calculate_routing_usage(CHANY); |
| |
| auto chanx_avail = calculate_routing_avail(CHANX); |
| auto chany_avail = calculate_routing_avail(CHANY); |
| |
| float min_util = 0.; |
| float max_util = -std::numeric_limits<float>::infinity(); |
| for (size_t x = 0; x < device_ctx.grid.width() - 1; ++x) { |
| for (size_t y = 0; y < device_ctx.grid.height() - 1; ++y) { |
| max_util = std::max(max_util, routing_util(chanx_usage[x][y], chanx_avail[x][y])); |
| max_util = std::max(max_util, routing_util(chany_usage[x][y], chany_avail[x][y])); |
| } |
| } |
| max_util = std::max(max_util, 1.f); |
| |
| std::unique_ptr<vtr::ColorMap> cmap = std::make_unique<vtr::PlasmaColorMap>(min_util, max_util); |
| |
| float tile_width = draw_coords->get_tile_width(); |
| float tile_height = draw_coords->get_tile_height(); |
| |
| float ALPHA = 0.95; |
| if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_OVER_BLOCKS) { |
| ALPHA = 1.; |
| } |
| |
| for (size_t x = 0; x < device_ctx.grid.width() - 1; ++x) { |
| for (size_t y = 0; y < device_ctx.grid.height() - 1; ++y) { |
| float sb_util = 0; |
| float chanx_util = 0; |
| float chany_util = 0; |
| int chan_count = 0; |
| if (x > 0) { |
| chanx_util = routing_util(chanx_usage[x][y], chanx_avail[x][y]); |
| t_color chanx_color = to_t_color(cmap->color(chanx_util)); |
| chanx_color.alpha *= ALPHA; |
| setcolor(chanx_color); |
| t_bound_box bb(draw_coords->tile_x[x], draw_coords->tile_y[y] + 1 * tile_height, |
| draw_coords->tile_x[x] + 1 * tile_width, draw_coords->tile_y[y + 1]); |
| fillrect(bb); |
| |
| setcolor(BLACK); |
| if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE) { |
| drawtext_in(bb, vtr::string_fmt("%.2f", chanx_util).c_str()); |
| } else if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) { |
| drawtext_in(bb, vtr::string_fmt("%.2f = %.0f / %.0f", chanx_util, chanx_usage[x][y], chanx_avail[x][y]).c_str()); |
| } |
| |
| sb_util += chanx_util; |
| ++chan_count; |
| } |
| |
| if (y > 0) { |
| chany_util = routing_util(chany_usage[x][y], chany_avail[x][y]); |
| t_color chany_color = to_t_color(cmap->color(chany_util)); |
| chany_color.alpha *= ALPHA; |
| setcolor(chany_color); |
| t_bound_box bb(draw_coords->tile_x[x] + 1 * tile_width, draw_coords->tile_y[y], |
| draw_coords->tile_x[x + 1], draw_coords->tile_y[y] + 1 * tile_height); |
| fillrect(bb); |
| |
| setcolor(BLACK); |
| if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE) { |
| drawtext_in(bb, vtr::string_fmt("%.2f", chany_util).c_str()); |
| } else if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) { |
| drawtext_in(bb, vtr::string_fmt("%.2f = %.0f / %.0f", chany_util, chany_usage[x][y], chany_avail[x][y]).c_str()); |
| } |
| |
| sb_util += chany_util; |
| ++chan_count; |
| } |
| |
| //For now SB util is just average of surrounding channels |
| //TODO: calculate actual usage |
| sb_util += routing_util(chanx_usage[x + 1][y], chanx_avail[x + 1][y]); |
| chan_count += 1; |
| sb_util += routing_util(chany_usage[x][y + 1], chany_avail[x][y + 1]); |
| chan_count += 1; |
| |
| VTR_ASSERT(chan_count > 0); |
| sb_util /= chan_count; |
| t_color sb_color = to_t_color(cmap->color(sb_util)); |
| sb_color.alpha *= ALPHA; |
| setcolor(sb_color); |
| t_bound_box bb(draw_coords->tile_x[x] + 1 * tile_width, draw_coords->tile_y[y] + 1 * tile_height, |
| draw_coords->tile_x[x + 1], draw_coords->tile_y[y + 1]); |
| fillrect(bb); |
| |
| //Draw over blocks |
| if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_OVER_BLOCKS) { |
| if (x < device_ctx.grid.width() - 2 && y < device_ctx.grid.height() - 2) { |
| t_bound_box bb2(draw_coords->tile_x[x + 1], draw_coords->tile_y[y + 1], |
| draw_coords->tile_x[x + 1] + 1 * tile_width, draw_coords->tile_y[y + 1] + 1 * tile_width); |
| fillrect(bb2); |
| } |
| } |
| |
| setcolor(BLACK); |
| if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE |
| || draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) { |
| drawtext_in(bb, vtr::string_fmt("%.2f", sb_util).c_str()); |
| } |
| } |
| } |
| |
| draw_state->color_map = std::move(cmap); |
| } |
| |
| static float get_router_rr_cost(const t_rr_node_route_inf node_inf, e_draw_router_rr_cost draw_router_rr_cost) { |
| if (draw_router_rr_cost == DRAW_ROUTER_RR_COST_TOTAL) { |
| return node_inf.path_cost; |
| } else if (draw_router_rr_cost == DRAW_ROUTER_RR_COST_KNOWN) { |
| return node_inf.backward_path_cost; |
| } else if (draw_router_rr_cost == DRAW_ROUTER_RR_COST_EXPECTED) { |
| return node_inf.path_cost - node_inf.backward_path_cost; |
| } |
| |
| VPR_THROW(VPR_ERROR_DRAW, "Invalid Router RR cost drawing type"); |
| } |
| |
| static void draw_router_rr_costs() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| if (draw_state->show_router_rr_cost == DRAW_NO_ROUTER_RR_COST) { |
| return; |
| } |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| auto& routing_ctx = g_vpr_ctx.routing(); |
| |
| std::vector<float> rr_costs(device_ctx.rr_nodes.size()); |
| |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { |
| float cost = get_router_rr_cost(routing_ctx.rr_node_route_inf[inode], draw_state->show_router_rr_cost); |
| rr_costs[inode] = cost; |
| } |
| |
| bool all_nan = true; |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); ++inode) { |
| if (std::isinf(rr_costs[inode])) { |
| rr_costs[inode] = NAN; |
| } else { |
| all_nan = false; |
| } |
| } |
| |
| if (!all_nan) { |
| draw_rr_costs(rr_costs, false); |
| } |
| |
| if (draw_state->show_router_rr_cost == DRAW_ROUTER_RR_COST_TOTAL) { |
| update_message("Routing Expected Total Cost (known + estimate)"); |
| } else if (draw_state->show_router_rr_cost == DRAW_ROUTER_RR_COST_KNOWN) { |
| update_message("Routing Known Cost (from source to node)"); |
| } else { |
| update_message("Routing Expected Cost (from node to target)"); |
| VTR_ASSERT(draw_state->show_router_rr_cost == DRAW_ROUTER_RR_COST_EXPECTED); |
| } |
| } |
| |
| static void draw_rr_costs(const std::vector<float>& rr_costs, bool lowest_cost_first) { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| /* Draws routing costs */ |
| |
| auto& device_ctx = g_vpr_ctx.device(); |
| |
| setlinewidth(2); |
| |
| VTR_ASSERT(rr_costs.size() == device_ctx.rr_nodes.size()); |
| |
| float min_cost = std::numeric_limits<float>::infinity(); |
| float max_cost = -min_cost; |
| for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) { |
| if (std::isnan(rr_costs[inode])) continue; |
| |
| min_cost = std::min(min_cost, rr_costs[inode]); |
| max_cost = std::max(max_cost, rr_costs[inode]); |
| } |
| if (min_cost == std::numeric_limits<float>::infinity()) min_cost = 0; |
| if (max_cost == -std::numeric_limits<float>::infinity()) max_cost = 0; |
| std::unique_ptr<vtr::ColorMap> cmap = std::make_unique<vtr::PlasmaColorMap>(min_cost, max_cost); |
| |
| //Draw the nodes in ascending order of value, this ensures high valued nodes |
| //are not overdrawn by lower value ones (e.g. when zoomed-out far) |
| std::vector<int> nodes(device_ctx.rr_nodes.size()); |
| std::iota(nodes.begin(), nodes.end(), 0); |
| auto cmp_ascending_cost = [&](int lhs_node, int rhs_node) { |
| if (lowest_cost_first) { |
| return rr_costs[lhs_node] > rr_costs[rhs_node]; |
| } |
| return rr_costs[lhs_node] < rr_costs[rhs_node]; |
| }; |
| std::sort(nodes.begin(), nodes.end(), cmp_ascending_cost); |
| |
| for (int inode : nodes) { |
| float cost = rr_costs[inode]; |
| if (std::isnan(cost)) continue; |
| |
| t_color color = to_t_color(cmap->color(cost)); |
| |
| switch (device_ctx.rr_nodes[inode].type()) { |
| case CHANX: //fallthrough |
| case CHANY: |
| draw_rr_chan(inode, color); |
| draw_rr_edges(inode); |
| break; |
| |
| case IPIN: //fallthrough |
| draw_rr_pin(inode, color); |
| break; |
| case OPIN: |
| draw_rr_pin(inode, color); |
| draw_rr_edges(inode); |
| break; |
| case SOURCE: |
| case SINK: |
| color.alpha *= 0.8; |
| draw_rr_src_sink(inode, color); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| draw_state->color_map = std::move(cmap); |
| } |
| |
| static void draw_placement_macros() { |
| t_draw_state* draw_state = get_draw_state_vars(); |
| |
| if (draw_state->show_placement_macros == DRAW_NO_PLACEMENT_MACROS) { |
| return; |
| } |
| |
| t_draw_coords* draw_coords = get_draw_coords_vars(); |
| |
| auto& place_ctx = g_vpr_ctx.placement(); |
| auto& cluster_ctx = g_vpr_ctx.clustering(); |
| for (size_t imacro = 0; imacro < place_ctx.pl_macros.size(); ++imacro) { |
| const t_pl_macro* pl_macro = &place_ctx.pl_macros[imacro]; |
| |
| //TODO: for now we just draw the bounding box of the macro, which is incorrect for non-rectangular macros... |
| int xlow = std::numeric_limits<int>::max(); |
| int ylow = std::numeric_limits<int>::max(); |
| int xhigh = std::numeric_limits<int>::min(); |
| int yhigh = std::numeric_limits<int>::min(); |
| |
| int x_root = OPEN; |
| int y_root = OPEN; |
| for (size_t imember = 0; imember < pl_macro->members.size(); ++imember) { |
| const t_pl_macro_member* member = &pl_macro->members[imember]; |
| |
| ClusterBlockId blk = member->blk_index; |
| |
| if (imember == 0) { |
| x_root = place_ctx.block_locs[blk].loc.x; |
| y_root = place_ctx.block_locs[blk].loc.y; |
| } |
| |
| int x = x_root + member->offset.x; |
| int y = y_root + member->offset.y; |
| |
| xlow = std::min(xlow, x); |
| ylow = std::min(ylow, y); |
| xhigh = std::max(xhigh, x + cluster_ctx.clb_nlist.block_type(blk)->width); |
| yhigh = std::max(yhigh, y + cluster_ctx.clb_nlist.block_type(blk)->height); |
| } |
| |
| int draw_xlow = draw_coords->tile_x[xlow]; |
| int draw_ylow = draw_coords->tile_y[ylow]; |
| int draw_xhigh = draw_coords->tile_x[xhigh]; |
| int draw_yhigh = draw_coords->tile_y[yhigh]; |
| |
| setcolor(RED); |
| drawrect(draw_xlow, draw_ylow, draw_xhigh, draw_yhigh); |
| |
| t_color fill = SKYBLUE; |
| fill.alpha *= 0.3; |
| setcolor(fill); |
| fillrect(draw_xlow, draw_ylow, draw_xhigh, draw_yhigh); |
| } |
| } |