blob: 1e099500fc116002eb61b5cbc19af57ec3d5c1a7 [file] [log] [blame]
/*********************************** Top-level Summary *************************************
* This is VPR's main graphics application program. The program interacts with ezgl/graphics.hpp,
* which provides an API for displaying graphics on both X11 and Win32. The most important
* subroutine in this file is draw_main_canvas(), which is a callback function that will be called
* whenever the screen needs to be updated. Then, draw_main_canvas() will decide what
* drawing subroutines to call depending on whether PLACEMENT or ROUTING is shown on screen.
* The initial_setup_X() functions link the menu button signals to the corresponding drawing functions.
* As a note, looks into draw_global.c for understanding the data structures associated with drawing->
*
*
* Authors: Vaughn Betz, Long Yu (Mike) Wang, Dingyu (Tina) Yang
* Last updated: June 2019
*/
#include <cstdio>
#include <cfloat>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <sstream>
#include <array>
#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 "draw_color.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"
#include "search_bar.h"
#include "save_graphics.h"
#include "timing_info.h"
#include "physical_types.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
#ifndef NO_GRAPHICS
//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"
# include "buttons.h"
/****************************** Define Macros *******************************/
# define DEFAULT_RR_NODE_COLOR ezgl::BLACK
//#define TIME_DRAWSCREEN /* Enable if want to track runtime for drawscreen() */
/********************** Subroutines local to this module ********************/
static void drawplace(ezgl::renderer* g);
static void drawnets(ezgl::renderer* g);
static void drawroute(enum e_draw_net_type draw_net_type, ezgl::renderer* g);
static void draw_congestion(ezgl::renderer* g);
static void draw_routing_costs(ezgl::renderer* g);
static void draw_routing_bb(ezgl::renderer* g);
static void draw_routing_util(ezgl::renderer* g);
static void draw_crit_path(ezgl::renderer* g);
static void draw_placement_macros(ezgl::renderer* g);
void act_on_key_press(ezgl::application* /*app*/, GdkEventKey* /*event*/, char* key_name);
void act_on_mouse_press(ezgl::application* app, GdkEventButton* event, double x, double y);
void act_on_mouse_move(ezgl::application* app, GdkEventButton* event, double x, double y);
static void draw_routed_net(ClusterNetId net, ezgl::renderer* g);
void draw_partial_route(const std::vector<int>& rr_nodes_to_draw, ezgl::renderer* g);
static void draw_rr(ezgl::renderer* g);
static void draw_rr_edges(int from_node, ezgl::renderer* g);
static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g);
static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g);
static void draw_rr_src_sink(int inode, ezgl::color color, ezgl::renderer* g);
static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g);
static void draw_x(float x, float y, float size, ezgl::renderer* g);
static void draw_pin_to_pin(int opin, int ipin, ezgl::renderer* g);
static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, bool buffered, bool switch_configurable, ezgl::renderer* g);
static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g);
static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g);
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, ezgl::renderer* g);
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 int draw_check_rr_node_hit(float click_x, float click_y);
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 highlight_blocks(double x, double y);
static void draw_reset_blk_colors();
static void draw_reset_blk_color(ClusterBlockId blk_id);
static inline void draw_mux_with_size(ezgl::point2d origin, e_side orientation, float height, int size, ezgl::renderer* g);
static inline ezgl::rectangle draw_mux(ezgl::point2d origin, e_side orientation, float height, ezgl::renderer* g);
static inline ezgl::rectangle draw_mux(ezgl::point2d origin, e_side orientation, float height, float width, float height_scale, ezgl::renderer* g);
static void draw_flyline_timing_edge(ezgl::point2d start, ezgl::point2d end, float incr_delay, ezgl::renderer* g);
static void draw_routed_timing_edge(tatum::NodeId start_tnode, tatum::NodeId end_tnode, float incr_delay, ezgl::color color, ezgl::renderer* g);
static void draw_routed_timing_edge_connection(tatum::NodeId src_tnode, tatum::NodeId sink_tnode, ezgl::color color, ezgl::renderer* g);
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 t_edge_size find_edge(int prev_inode, int inode);
static void draw_color_map_legend(const vtr::ColorMap& cmap, ezgl::renderer* g);
ezgl::color get_block_type_color(t_physical_tile_type_ptr type);
ezgl::color lighten_color(ezgl::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(ezgl::renderer* g);
static void draw_rr_costs(ezgl::renderer* g, const std::vector<float>& rr_costs, bool lowest_cost_first = true);
void draw_main_canvas(ezgl::renderer* g);
void initial_setup_NO_PICTURE_to_PLACEMENT(ezgl::application* app, bool is_new_window);
void initial_setup_NO_PICTURE_to_PLACEMENT_with_crit_path(ezgl::application* app, bool is_new_window);
void initial_setup_PLACEMENT_to_ROUTING(ezgl::application* app, bool is_new_window);
void initial_setup_ROUTING_to_PLACEMENT(ezgl::application* app, bool is_new_window);
void initial_setup_NO_PICTURE_to_ROUTING(ezgl::application* app, bool is_new_window);
void initial_setup_NO_PICTURE_to_ROUTING_with_crit_path(ezgl::application* app, bool is_new_window);
void toggle_window_mode(GtkWidget* /*widget*/, ezgl::application* /*app*/);
void setup_default_ezgl_callbacks(ezgl::application* app);
void set_force_pause(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/);
/************************** File Scope Variables ****************************/
//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<ezgl::color> kelly_max_contrast_colors = {
//ezgl::color(242, 243, 244), //white: skip white since it doesn't contrast well with VPR's light background
ezgl::color(34, 34, 34), //black
ezgl::color(243, 195, 0), //yellow
ezgl::color(135, 86, 146), //purple
ezgl::color(243, 132, 0), //orange
ezgl::color(161, 202, 241), //light blue
ezgl::color(190, 0, 50), //red
ezgl::color(194, 178, 128), //buf
ezgl::color(132, 132, 130), //gray
ezgl::color(0, 136, 86), //green
ezgl::color(230, 143, 172), //purplish pink
ezgl::color(0, 103, 165), //blue
ezgl::color(249, 147, 121), //yellowish pink
ezgl::color(96, 78, 151), //violet
ezgl::color(246, 166, 0), //orange yellow
ezgl::color(179, 68, 108), //purplish red
ezgl::color(220, 211, 0), //greenish yellow
ezgl::color(136, 45, 23), //redish brown
ezgl::color(141, 182, 0), //yellow green
ezgl::color(101, 69, 34), //yellowish brown
ezgl::color(226, 88, 34), //reddish orange
ezgl::color(43, 61, 38) //olive green
};
ezgl::application::settings settings("/ezgl/main.ui",
"MainWindow",
"MainCanvas",
setup_default_ezgl_callbacks);
ezgl::application application(settings);
bool window_mode = false;
bool window_point_1_collected = false;
ezgl::point2d point_1(0, 0);
ezgl::rectangle initial_world;
std::string rr_highlight_message;
#endif // NO_GRAPHICS
/********************** Subroutine definitions ******************************/
void init_graphics_state(bool show_graphics_val, int gr_automode_val, enum e_route_type route_type, bool save_graphics) {
#ifndef NO_GRAPHICS
/* 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;
draw_state->save_graphics = save_graphics;
#else
(void)show_graphics_val;
(void)gr_automode_val;
(void)route_type;
(void)save_graphics;
#endif // NO_GRAPHICS
}
#ifndef NO_GRAPHICS
void draw_main_canvas(ezgl::renderer* g) {
t_draw_state* draw_state = get_draw_state_vars();
g->set_font_size(14);
draw_block_pin_util();
drawplace(g);
draw_internal_draw_subblk(g);
if (draw_state->pic_on_screen == PLACEMENT) {
switch (draw_state->show_nets) {
case DRAW_NETS:
drawnets(g);
break;
case DRAW_LOGICAL_CONNECTIONS:
break;
default:
break;
}
} else { /* ROUTING on screen */
switch (draw_state->show_nets) {
case DRAW_NETS:
drawroute(ALL_NETS, g);
break;
case DRAW_LOGICAL_CONNECTIONS:
// fall through
default:
draw_rr(g);
break;
}
draw_congestion(g);
draw_routing_costs(g);
draw_router_rr_costs(g);
draw_routing_util(g);
draw_routing_bb(g);
}
draw_placement_macros(g);
draw_crit_path(g);
draw_logical_connections(g);
if (draw_state->color_map) {
draw_color_map_legend(*draw_state->color_map, g);
draw_state->color_map.reset(); //Free color map in preparation for next redraw
}
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* NO_PICTURE_to_PLACEMENT */
void initial_setup_NO_PICTURE_to_PLACEMENT(ezgl::application* app, bool is_new_window) {
if (!is_new_window) return;
//button to enter window_mode, created in main.ui
GtkButton* window = (GtkButton*)app->get_object("Window");
gtk_button_set_label(window, "Window");
g_signal_connect(window, "clicked", G_CALLBACK(toggle_window_mode), app);
//button to search, created in main.ui
GtkButton* search = (GtkButton*)app->get_object("Search");
gtk_button_set_label(search, "Search");
g_signal_connect(search, "clicked", G_CALLBACK(search_and_highlight), app);
//button for save graphcis, created in main.ui
GtkButton* save = (GtkButton*)app->get_object("SaveGraphics");
g_signal_connect(save, "clicked", G_CALLBACK(save_graphics_dialog_box), app);
//combo box for search type, created in main.ui
GObject* search_type = (GObject*)app->get_object("SearchType");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Block ID"); // index 0
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Block Name"); // index 1
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Net ID"); // index 2
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Net Name"); // index 3
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "RR Node ID"); // index 4
gtk_combo_box_set_active((GtkComboBox*)search_type, 0); // default set to Block ID which has an index 0
button_for_toggle_nets();
button_for_toggle_blk_internal();
button_for_toggle_block_pin_util();
button_for_toggle_placement_macros();
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* NO_PICTURE_to_PLACEMENT_with_crit_path */
void initial_setup_NO_PICTURE_to_PLACEMENT_with_crit_path(ezgl::application* app, bool is_new_window) {
initial_setup_NO_PICTURE_to_PLACEMENT(app, is_new_window);
button_for_toggle_crit_path();
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* PLACEMENT_to_ROUTING */
void initial_setup_PLACEMENT_to_ROUTING(ezgl::application* app, bool is_new_window) {
initial_setup_NO_PICTURE_to_PLACEMENT_with_crit_path(app, is_new_window);
button_for_toggle_rr();
button_for_toggle_congestion();
button_for_toggle_congestion_cost();
button_for_toggle_routing_bounding_box();
button_for_toggle_routing_util();
button_for_toggle_router_rr_costs();
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* ROUTING_to_PLACEMENT */
void initial_setup_ROUTING_to_PLACEMENT(ezgl::application* app, bool is_new_window) {
initial_setup_PLACEMENT_to_ROUTING(app, is_new_window);
std::string toggle_rr = "toggle_rr";
std::string toggle_congestion = "toggle_congestion";
std::string toggle_routing_congestion_cost = "toggle_routing_congestion_cost";
std::string toggle_routing_bounding_box = "toggle_routing_bounding_box";
std::string toggle_routing_util = "toggle_rr";
std::string toggle_router_rr_costs = "toggle_router_rr_costs";
delete_button(toggle_rr.c_str());
delete_button(toggle_congestion.c_str());
delete_button(toggle_routing_congestion_cost.c_str());
delete_button(toggle_routing_bounding_box.c_str());
delete_button(toggle_routing_util.c_str());
delete_button(toggle_router_rr_costs.c_str());
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* NO_PICTURE_to_ROUTING */
void initial_setup_NO_PICTURE_to_ROUTING(ezgl::application* app, bool is_new_window) {
if (!is_new_window) return;
GtkButton* window = (GtkButton*)app->get_object("Window");
gtk_button_set_label(window, "Window");
g_signal_connect(window, "clicked", G_CALLBACK(toggle_window_mode), app);
GtkButton* search = (GtkButton*)app->get_object("Search");
gtk_button_set_label(search, "Search");
g_signal_connect(search, "clicked", G_CALLBACK(search_and_highlight), app);
GtkButton* save = (GtkButton*)app->get_object("SaveGraphics");
g_signal_connect(save, "clicked", G_CALLBACK(save_graphics_dialog_box), app);
GObject* search_type = (GObject*)app->get_object("SearchType");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Block ID");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Block Name");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Net ID");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "Net Name");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(search_type), "RR Node ID");
button_for_toggle_nets();
button_for_toggle_blk_internal();
button_for_toggle_block_pin_util();
button_for_toggle_placement_macros();
button_for_toggle_rr();
button_for_toggle_congestion();
button_for_toggle_congestion_cost();
button_for_toggle_routing_bounding_box();
button_for_toggle_routing_util();
button_for_toggle_router_rr_costs();
}
/* function below intializes the interface window with a set of buttons and links
* signals to corresponding functions for situation where the window is opened from
* NO_PICTURE_to_ROUTING_with_crit_path */
void initial_setup_NO_PICTURE_to_ROUTING_with_crit_path(ezgl::application* app, bool is_new_window) {
initial_setup_NO_PICTURE_to_ROUTING(app, is_new_window);
button_for_toggle_crit_path();
}
#endif //NO_GRAPHICS
void update_screen(ScreenUpdatePriority priority, const char* msg, enum pic_type pic_on_screen_val, std::shared_ptr<SetupTimingInfo> setup_timing_info) {
#ifndef NO_GRAPHICS
/* 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)
ezgl::set_disable_event_loop(true);
else
ezgl::set_disable_event_loop(false);
//Has the user asked us to pause at the next screen updated?
if (int(priority) >= draw_state->gr_automode || draw_state->forced_pause) {
if (draw_state->forced_pause) {
VTR_LOG("Pausing in interactive graphics (user pressed 'Pause')\n");
draw_state->forced_pause = false; //Reset pause flag
}
vtr::strncpy(draw_state->default_message, msg, vtr::bufsize);
/* 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) {
draw_state->pic_on_screen = pic_on_screen_val;
//Placement first to open
if (setup_timing_info) {
draw_state->setup_timing_info = setup_timing_info;
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_placement";
save_graphics(extension, file_name);
}
application.run(initial_setup_NO_PICTURE_to_PLACEMENT_with_crit_path, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
} else {
draw_state->setup_timing_info = setup_timing_info;
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_placement";
save_graphics(extension, file_name);
}
application.run(initial_setup_NO_PICTURE_to_PLACEMENT, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
}
} else if (pic_on_screen_val == ROUTING && draw_state->pic_on_screen == PLACEMENT) {
//Routing, opening after placement
draw_state->setup_timing_info = setup_timing_info;
draw_state->pic_on_screen = pic_on_screen_val;
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_routing";
save_graphics(extension, file_name);
}
application.run(initial_setup_PLACEMENT_to_ROUTING, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
} else if (pic_on_screen_val == PLACEMENT && draw_state->pic_on_screen == ROUTING) {
draw_state->setup_timing_info = setup_timing_info;
draw_state->pic_on_screen = pic_on_screen_val;
//Placement, opening after routing
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_placement";
save_graphics(extension, file_name);
}
application.run(initial_setup_ROUTING_to_PLACEMENT, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
} else if (pic_on_screen_val == ROUTING
&& draw_state->pic_on_screen == NO_PICTURE) {
draw_state->pic_on_screen = pic_on_screen_val;
//Routing opening first
if (setup_timing_info) {
draw_state->setup_timing_info = setup_timing_info;
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_routing";
save_graphics(extension, file_name);
}
application.run(initial_setup_NO_PICTURE_to_ROUTING_with_crit_path, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
} else {
draw_state->setup_timing_info = setup_timing_info;
application.add_canvas("MainCanvas", draw_main_canvas, initial_world);
if (draw_state->save_graphics) {
std::string extension = "pdf";
std::string file_name = "vpr_routing";
save_graphics(extension, file_name);
}
application.run(initial_setup_NO_PICTURE_to_ROUTING, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
}
}
} else {
//No change (e.g. paused)
application.run(nullptr, act_on_mouse_press, act_on_mouse_move, act_on_key_press);
}
}
if (draw_state->show_graphics) {
application.update_message(msg);
application.refresh_drawing();
application.flush_drawing();
}
#else
(void)setup_timing_info;
(void)priority;
(void)msg;
(void)pic_on_screen_val;
#endif //NO_GRAPHICS
}
#ifndef NO_GRAPHICS
void toggle_window_mode(GtkWidget* /*widget*/, ezgl::application* /*app*/) {
window_mode = true;
}
void toggle_nets(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_nets button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
// get the pointer to the toggle_nets button
std::string button_name = "toggle_nets";
auto toggle_nets = find_button(button_name.c_str());
// use the pointer to get the active text
enum e_draw_nets new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_nets));
// assign corresponding enum value to draw_state->show_nets
if (strcmp(combo_box_content, "None") == 0)
new_state = DRAW_NO_NETS;
else if (strcmp(combo_box_content, "Nets") == 0) {
new_state = DRAW_NETS;
} else { // "Logical Connections"
new_state = DRAW_LOGICAL_CONNECTIONS;
}
draw_state->reset_nets_congestion_and_rr();
draw_state->show_nets = new_state;
//free dynamically allocated pointers
g_free(combo_box_content);
//redraw
application.update_message(draw_state->default_message);
application.refresh_drawing();
}
void toggle_rr(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_rr button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_rr";
auto toggle_rr = find_button(button_name.c_str());
enum e_draw_rr_toggle new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_rr));
if (strcmp(combo_box_content, "None") == 0)
new_state = DRAW_NO_RR;
else if (strcmp(combo_box_content, "Nodes RR") == 0)
new_state = DRAW_NODES_RR;
else if (strcmp(combo_box_content, "Nodes and SBox RR") == 0)
new_state = DRAW_NODES_AND_SBOX_RR;
else if (strcmp(combo_box_content, "All but Buffers RR") == 0)
new_state = DRAW_ALL_BUT_BUFFERS_RR;
else // all rr
new_state = DRAW_ALL_RR;
//free dynamically allocated pointers
g_free(combo_box_content);
draw_state->reset_nets_congestion_and_rr();
draw_state->draw_rr_toggle = new_state;
application.update_message(draw_state->default_message);
application.refresh_drawing();
}
void toggle_congestion(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_congestion button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_congestion";
auto toggle_congestion = find_button(button_name.c_str());
enum e_draw_congestion new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_congestion));
if (strcmp(combo_box_content, "None") == 0)
new_state = DRAW_NO_CONGEST;
else if (strcmp(combo_box_content, "Congested") == 0)
new_state = DRAW_CONGESTED;
else // congested with nets
new_state = DRAW_CONGESTED_WITH_NETS;
draw_state->reset_nets_congestion_and_rr();
draw_state->show_congestion = new_state;
if (draw_state->show_congestion == DRAW_NO_CONGEST) {
application.update_message(draw_state->default_message);
}
g_free(combo_box_content);
application.refresh_drawing();
}
void toggle_routing_congestion_cost(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_routing_congestion_cost button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_routing_congestion_cost";
auto toggle_routing_congestion_cost = find_button(button_name.c_str());
enum e_draw_routing_costs new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_routing_congestion_cost));
if (strcmp(combo_box_content, "None") == 0)
new_state = DRAW_NO_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Total Routing Costs") == 0)
new_state = DRAW_TOTAL_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Log Total Routing Costs") == 0)
new_state = DRAW_LOG_TOTAL_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Acc Routing Costs") == 0)
new_state = DRAW_ACC_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Log Acc Routing Costs") == 0)
new_state = DRAW_LOG_ACC_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Pres Routing Costs") == 0)
new_state = DRAW_PRES_ROUTING_COSTS;
else if (strcmp(combo_box_content, "Log Pres Routing Costs") == 0)
new_state = DRAW_LOG_PRES_ROUTING_COSTS;
else
new_state = DRAW_BASE_ROUTING_COSTS;
draw_state->reset_nets_congestion_and_rr();
draw_state->show_routing_costs = new_state;
g_free(combo_box_content);
if (draw_state->show_routing_costs == DRAW_NO_ROUTING_COSTS) {
application.update_message(draw_state->default_message);
}
application.refresh_drawing();
}
void toggle_routing_bounding_box(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_routing_bounding_box button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
auto& route_ctx = g_vpr_ctx.routing();
// get the pointer to the toggle_routing_bounding_box button
std::string button_name = "toggle_routing_bounding_box";
auto toggle_routing_bounding_box = find_button(button_name.c_str());
if (route_ctx.route_bb.size() == 0)
return; //Nothing to draw
// use the pointer to get the active value
int new_value = gtk_spin_button_get_value_as_int((GtkSpinButton*)toggle_routing_bounding_box);
// assign value to draw_state->show_routing_bb, bound check + set OPEN when it's -1 (draw nothing)
if (new_value < -1)
draw_state->show_routing_bb = -1;
else if (new_value == -1)
draw_state->show_routing_bb = OPEN;
else if (new_value >= (int)(route_ctx.route_bb.size()))
draw_state->show_routing_bb = route_ctx.route_bb.size() - 1;
else
draw_state->show_routing_bb = new_value;
//redraw
if ((int)(draw_state->show_routing_bb) == (int)((int)(route_ctx.route_bb.size()) - 1)) {
application.update_message(draw_state->default_message);
}
application.refresh_drawing();
}
void toggle_routing_util(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_routing_util button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_routing_util";
auto toggle_routing_util = find_button(button_name.c_str());
enum e_draw_routing_util new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_routing_util));
if (strcmp(combo_box_content, "None") == 0)
new_state = DRAW_NO_ROUTING_UTIL;
else if (strcmp(combo_box_content, "Routing Util") == 0)
new_state = DRAW_ROUTING_UTIL;
else if (strcmp(combo_box_content, "Routing Util with Value") == 0)
new_state = DRAW_ROUTING_UTIL_WITH_VALUE;
else if (strcmp(combo_box_content, "Routing Util with Formula") == 0)
new_state = DRAW_ROUTING_UTIL_WITH_FORMULA;
else
new_state = DRAW_ROUTING_UTIL_OVER_BLOCKS;
g_free(combo_box_content);
draw_state->show_routing_util = new_state;
if (draw_state->show_routing_util == DRAW_NO_ROUTING_UTIL) {
application.update_message(draw_state->default_message);
}
application.refresh_drawing();
}
void toggle_blk_internal(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_blk_internal button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_blk_internal";
auto toggle_blk_internal = find_button(button_name.c_str());
int new_value = gtk_spin_button_get_value_as_int((GtkSpinButton*)toggle_blk_internal);
if (new_value < 0)
draw_state->show_blk_internal = 0;
else if (new_value >= draw_state->max_sub_blk_lvl)
draw_state->show_blk_internal = draw_state->max_sub_blk_lvl - 1;
else
draw_state->show_blk_internal = new_value;
application.refresh_drawing();
}
void toggle_block_pin_util(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_block_pin_util button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_block_pin_util";
auto toggle_block_pin_util = find_button(button_name.c_str());
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_block_pin_util));
if (strcmp(combo_box_content, "None") == 0) {
draw_state->show_blk_pin_util = DRAW_NO_BLOCK_PIN_UTIL;
draw_reset_blk_colors();
application.update_message(draw_state->default_message);
} else if (strcmp(combo_box_content, "All") == 0)
draw_state->show_blk_pin_util = DRAW_BLOCK_PIN_UTIL_TOTAL;
else if (strcmp(combo_box_content, "Inputs") == 0)
draw_state->show_blk_pin_util = DRAW_BLOCK_PIN_UTIL_INPUTS;
else
draw_state->show_blk_pin_util = DRAW_BLOCK_PIN_UTIL_OUTPUTS;
g_free(combo_box_content);
application.refresh_drawing();
}
void toggle_placement_macros(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_placement_macros button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_placement_macros";
auto toggle_placement_macros = find_button(button_name.c_str());
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_placement_macros));
if (strcmp(combo_box_content, "None") == 0)
draw_state->show_placement_macros = DRAW_NO_PLACEMENT_MACROS;
else
draw_state->show_placement_macros = DRAW_PLACEMENT_MACROS;
g_free(combo_box_content);
application.refresh_drawing();
}
void toggle_crit_path(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_crit_path button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_crit_path";
auto toggle_crit_path = find_button(button_name.c_str());
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_crit_path));
if (strcmp(combo_box_content, "None") == 0) {
draw_state->show_crit_path = DRAW_NO_CRIT_PATH;
} else if (strcmp(combo_box_content, "Crit Path Flylines") == 0)
draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES;
else if (strcmp(combo_box_content, "Crit Path Flylines Delays") == 0)
draw_state->show_crit_path = DRAW_CRIT_PATH_FLYLINES_DELAYS;
else if (strcmp(combo_box_content, "Crit Path Routing") == 0)
draw_state->show_crit_path = DRAW_CRIT_PATH_ROUTING;
else // Crit Path Routing Delays
draw_state->show_crit_path = DRAW_CRIT_PATH_ROUTING_DELAYS;
g_free(combo_box_content);
application.refresh_drawing();
}
void toggle_router_rr_costs(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
/* this is the callback function for runtime created toggle_router_rr_costs button
* which is written in button.cpp */
t_draw_state* draw_state = get_draw_state_vars();
std::string button_name = "toggle_router_rr_costs";
auto toggle_router_rr_costs = find_button(button_name.c_str());
e_draw_router_rr_cost new_state;
gchar* combo_box_content = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(toggle_router_rr_costs));
if (strcmp(combo_box_content, "None") == 0) {
new_state = DRAW_NO_ROUTER_RR_COST;
} else if (strcmp(combo_box_content, "Total") == 0)
new_state = DRAW_ROUTER_RR_COST_TOTAL;
else if (strcmp(combo_box_content, "Known") == 0)
new_state = DRAW_ROUTER_RR_COST_KNOWN;
else
new_state = DRAW_ROUTER_RR_COST_EXPECTED;
g_free(combo_box_content);
draw_state->show_router_rr_cost = new_state;
if (draw_state->show_router_rr_cost == DRAW_NO_ROUTER_RR_COST) {
application.update_message(draw_state->default_message);
}
application.refresh_drawing();
}
#endif // NO_GRAPHICS
void alloc_draw_structs(const t_arch* arch) {
#ifndef NO_GRAPHICS
/* 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 */
#else
(void)arch;
#endif // NO_GRAPHICS
}
void free_draw_structs() {
#ifndef NO_GRAPHICS
/* 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;
}
#else
;
#endif /* NO_GRAPHICS */
}
void init_draw_coords(float width_val) {
#ifndef NO_GRAPHICS
/* 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 && !draw_state->save_graphics)
return; //do not initialize only if --disp off and --save_graphics off
/* 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 (const auto& type : device_ctx.physical_tile_types) {
auto num_pins = type.num_pins;
if (num_pins > 0) {
draw_coords->pin_size = std::min(draw_coords->pin_size,
(draw_coords->get_tile_width() / (4.0F * 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();
initial_world = ezgl::rectangle({-VISIBLE_MARGIN * draw_width, -VISIBLE_MARGIN * draw_height}, {(1. + VISIBLE_MARGIN) * draw_width, (1. + VISIBLE_MARGIN) * draw_height});
#else
(void)width_val;
#endif /* NO_GRAPHICS */
}
#ifndef NO_GRAPHICS
/* 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(ezgl::renderer* g) {
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;
g->set_line_width(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
ezgl::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);
}
g->set_color(block_color);
/* Get coords of current sub_tile */
ezgl::rectangle abs_clb_bbox = draw_coords->get_absolute_clb_bbox(i, j, k);
ezgl::point2d center = abs_clb_bbox.center();
g->fill_rectangle(abs_clb_bbox);
g->set_color(ezgl::BLACK);
g->set_line_dash((EMPTY_BLOCK_ID == bnum) ? ezgl::line_dash::asymmetric_5_3 : ezgl::line_dash::none);
g->draw_rectangle(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));
g->draw_text(center, name.c_str(), abs_clb_bbox.width(), abs_clb_bbox.height());
}
/* 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);
g->draw_text(center - ezgl::point2d(0, abs_clb_bbox.height() / 4),
block_type_loc.c_str(), abs_clb_bbox.width(), abs_clb_bbox.height());
}
}
}
}
}
static void drawnets(ezgl::renderer* g) {
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();
g->set_line_dash(ezgl::line_dash::none);
g->set_line_width(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 */
g->set_color(draw_state->net_color[net_id]);
b1 = cluster_ctx.clb_nlist.net_driver_block(net_id);
ezgl::point2d driver_center = draw_coords->get_absolute_clb_bbox(b1, cluster_ctx.clb_nlist.block_type(b1)).center();
for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) {
b2 = cluster_ctx.clb_nlist.pin_block(pin_id);
ezgl::point2d sink_center = draw_coords->get_absolute_clb_bbox(b2, cluster_ctx.clb_nlist.block_type(b2)).center();
g->draw_line(driver_center, sink_center);
/* Uncomment to draw a chain instead of a star. */
/* driver_center = sink_center; */
}
}
}
static void draw_congestion(ezgl::renderer* g) {
/* Draws all the overused routing resources (i.e. congestion) in various contrasting colors showing congestion ratio. */
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);
}
application.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]) {
ezgl::color color = kelly_max_contrast_colors[size_t(net) % kelly_max_contrast_colors.size()];
draw_state->net_color[net] = color;
}
}
g->set_line_width(0);
drawroute(HIGHLIGHTED, g);
//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 {
g->set_line_width(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);
ezgl::color color = to_ezgl_color(cmap->color(congestion_ratio));
switch (device_ctx.rr_nodes[inode].type()) {
case CHANX: //fallthrough
case CHANY:
draw_rr_chan(inode, color, g);
break;
case IPIN: //fallthrough
case OPIN:
draw_rr_pin(inode, color, g);
break;
default:
break;
}
}
draw_state->color_map = std::move(cmap);
}
static void draw_routing_costs(ezgl::renderer* g) {
/* Draws routing resource nodes colored according to their congestion costs */
t_draw_state* draw_state = get_draw_state_vars();
/* show_routing_costs controls whether the total/sum of the costs or individual
* cost components (base cost, accumulated cost, present cost) are shown, and
* whether colours are proportional to the node's cost or the logarithm of
* it's cost.*/
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();
g->set_line_width(0);
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);
}
application.update_message(msg);
draw_rr_costs(g, rr_node_costs, true);
}
static void draw_routing_bb(ezgl::renderer* g) {
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
double draw_xlow = draw_coords->tile_x[bb->xmin];
double draw_ylow = draw_coords->tile_y[bb->ymin];
double draw_xhigh = draw_coords->tile_x[bb->xmax] + 2 * draw_coords->get_tile_width();
double draw_yhigh = draw_coords->tile_y[bb->ymax] + 2 * draw_coords->get_tile_height();
g->set_color(blk_RED);
g->draw_rectangle({draw_xlow, draw_ylow}, {draw_xhigh, draw_yhigh});
ezgl::color fill = blk_SKYBLUE;
fill.alpha *= 0.3;
g->set_color(fill);
g->fill_rectangle({draw_xlow, draw_ylow}, {draw_xhigh, draw_yhigh});
draw_routed_net(net_id, g);
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)) + ")";
application.update_message(msg.c_str());
}
void draw_rr(ezgl::renderer* g) {
/* 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) {
g->set_line_width(3);
drawroute(HIGHLIGHTED, g);
g->set_line_width(0);
return;
}
g->set_line_dash(ezgl::line_dash::none);
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 = ezgl::PINK;
break;
case IPIN:
draw_state->draw_rr_node[inode].color = blk_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, g);
draw_rr_edges(inode, g);
break;
case CHANY:
draw_rr_chan(inode, draw_state->draw_rr_node[inode].color, g);
draw_rr_edges(inode, g);
break;
case IPIN:
draw_rr_pin(inode, draw_state->draw_rr_node[inode].color, g);
break;
case OPIN:
draw_rr_pin(inode, draw_state->draw_rr_node[inode].color, g);
draw_rr_edges(inode, g);
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, g);
}
static void draw_rr_chan(int inode, const ezgl::color color, ezgl::renderer* g) {
auto& device_ctx = g_vpr_ctx.device();
t_rr_type type = device_ctx.rr_nodes[inode].type();
VTR_ASSERT(type == CHANX || type == CHANY);
ezgl::rectangle 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
ezgl::point2d start = bound_box.bottom_left();
ezgl::point2d end = bound_box.top_right();
if (dir == DEC_DIRECTION) {
std::swap(start, end);
}
g->set_color(color);
if (color != DEFAULT_RR_NODE_COLOR) {
// If wire is highlighted, then draw with thicker linewidth.
g->set_line_width(3);
}
g->draw_line(start, end);
if (color != DEFAULT_RR_NODE_COLOR) {
// Revert width change
g->set_line_width(0);
}
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;
ezgl::color arrow_color = blk_LIGHTGREY;
ezgl::color text_color = ezgl::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;
}
ezgl::point2d arrow_loc_min(0, 0);
ezgl::point2d arrow_loc_max(0, 0);
if (type == CHANX) {
float sb_xmin = draw_coords->tile_x[k];
arrow_loc_min = {sb_xmin + arrow_offset, start.y};
float sb_xmax = draw_coords->tile_x[k] + draw_coords->get_tile_width();
arrow_loc_max = {sb_xmax - arrow_offset, start.y};
} else {
float sb_ymin = draw_coords->tile_y[k];
arrow_loc_min = {start.x, sb_ymin + arrow_offset};
float sb_ymax = draw_coords->tile_y[k] + draw_coords->get_tile_height();
arrow_loc_max = {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(), g);
}
} else {
//Draw arrows and label with switch point
if (k == coord_min) {
std::swap(arrow_color, text_color);
}
g->set_color(arrow_color);
draw_triangle_along_line(g, arrow_loc_min, start, end);
g->set_color(text_color);
ezgl::rectangle bbox(ezgl::point2d(arrow_loc_min.x - DEFAULT_ARROW_SIZE / 2, arrow_loc_min.y - DEFAULT_ARROW_SIZE / 4),
ezgl::point2d(arrow_loc_min.x + DEFAULT_ARROW_SIZE / 2, arrow_loc_min.y + DEFAULT_ARROW_SIZE / 4));
ezgl::point2d center = bbox.center();
g->draw_text(center, std::to_string(switchpoint_min), bbox.width(), bbox.height());
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(), g);
}
} else {
//Draw arrows and label with switch point
if (k == coord_max) {
std::swap(arrow_color, text_color);
}
g->set_color(arrow_color);
draw_triangle_along_line(g, arrow_loc_max, start, end);
g->set_color(text_color);
ezgl::rectangle bbox(ezgl::point2d(arrow_loc_max.x - DEFAULT_ARROW_SIZE / 2, arrow_loc_max.y - DEFAULT_ARROW_SIZE / 4),
ezgl::point2d(arrow_loc_max.x + DEFAULT_ARROW_SIZE / 2, arrow_loc_max.y + DEFAULT_ARROW_SIZE / 4));
ezgl::point2d center = bbox.center();
g->draw_text(center, std::to_string(switchpoint_max), bbox.width(), bbox.height());
if (k == coord_max) {
//Revert
std::swap(arrow_color, text_color);
}
}
}
g->set_color(color); //Ensure color is still set correctly if we drew any arrows/text
}
static void draw_rr_edges(int inode, ezgl::renderer* g) {
/* 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 (t_edge_size 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 == ezgl::MAGENTA) {
// If OPIN was clicked on, set color to fan-out
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
// If CHANX or CHANY got clicked, set color to fan-in
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else {
g->set_color(ezgl::PINK);
}
draw_pin_to_chan_edge(inode, to_node, g);
break;
case IPIN:
if (draw_state->draw_rr_node[inode].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else {
g->set_color(ezgl::MEDIUM_PURPLE);
}
draw_pin_to_pin(inode, to_node, g);
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 == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else {
g->set_color(blk_LIGHTSKYBLUE);
}
draw_pin_to_chan_edge(to_node, inode, g);
break;
case CHANX:
if (draw_state->draw_rr_node[inode].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else if (!edge_configurable) {
ezgl::color color = blk_DARKGREY;
g->set_color(color);
} else {
g->set_color(blk_DARKGREEN);
}
switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge);
draw_chanx_to_chanx_edge(inode, to_node,
to_ptc_num, switch_type, g);
break;
case CHANY:
if (draw_state->draw_rr_node[inode].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else if (!edge_configurable) {
g->set_color(blk_DARKGREY);
} else {
g->set_color(blk_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, g);
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 == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else {
g->set_color(blk_LIGHTSKYBLUE);
}
draw_pin_to_chan_edge(to_node, inode, g);
break;
case CHANX:
if (draw_state->draw_rr_node[inode].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else if (!edge_configurable) {
ezgl::color color = blk_DARKGREY;
g->set_color(color);
} else {
g->set_color(blk_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, g);
break;
case CHANY:
if (draw_state->draw_rr_node[inode].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[to_node].color;
g->set_color(color);
} else if (draw_state->draw_rr_node[to_node].color == ezgl::MAGENTA) {
ezgl::color color = draw_state->draw_rr_node[inode].color;
g->set_color(color);
} else if (!edge_configurable) {
ezgl::color color = blk_DARKGREY;
g->set_color(color);
} else {
g->set_color(blk_DARKGREEN);
}
switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge);
draw_chany_to_chany_edge(inode, to_node,
to_ptc_num, switch_type, g);
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, ezgl::renderer* g) {
/* Draws an X centered at (x,y). The width and height of the X are each *
* 2 * size. */
g->draw_line({x - size, y + size}, {x + size, y - size});
g->draw_line({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, ezgl::renderer* g) {
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;
ezgl::rectangle chanx_bbox;
ezgl::rectangle 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();
}
g->draw_line({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(), g);
} else {
draw_rr_switch(x2, y2, x1, y1, device_ctx.rr_switch_inf[switch_type].buffered(), device_ctx.rr_switch_inf[switch_type].configurable(), g);
}
}
}
static void draw_chanx_to_chanx_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g) {
/* 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;
ezgl::rectangle from_chan;
ezgl::rectangle 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();
}
}
}
g->draw_line({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(), g);
}
}
static void draw_chany_to_chany_edge(int from_node, int to_node, int to_track, short switch_type, ezgl::renderer* g) {
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;
ezgl::rectangle from_chan;
ezgl::rectangle 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 */
g->draw_line({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(), g);
}
}
/* 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.
*/
ezgl::rectangle draw_get_rr_chan_bbox(int inode) {
double left = 0, right = 0, top = 0, bottom = 0;
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:
left = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()];
right = draw_coords->tile_x[device_ctx.rr_nodes[inode].xhigh()]
+ draw_coords->get_tile_width();
bottom = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()]
+ draw_coords->get_tile_width()
+ (1. + device_ctx.rr_nodes[inode].ptc_num());
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:
left = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()]
+ draw_coords->get_tile_width()
+ (1. + device_ctx.rr_nodes[inode].ptc_num());
right = draw_coords->tile_x[device_ctx.rr_nodes[inode].xlow()]
+ draw_coords->get_tile_width()
+ (1. + device_ctx.rr_nodes[inode].ptc_num());
bottom = draw_coords->tile_y[device_ctx.rr_nodes[inode].ylow()];
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;
}
ezgl::rectangle bound_box({left, bottom}, {right, top});
return bound_box;
}
static void draw_rr_switch(float from_x, float from_y, float to_x, float to_y, bool buffered, bool configurable, ezgl::renderer* g) {
/* 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;
g->draw_arc({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(g, {from_x, from_y}, {to_x, to_y}, SB_EDGE_STRAIGHT_ARROW_POSITION);
} else {
//Turn connection
draw_triangle_along_line(g, {from_x, from_y}, {to_x, to_y}, SB_EDGE_TURN_ARROW_POSITION);
}
}
}
static void draw_rr_pin(int inode, const ezgl::color& color, ezgl::renderer* g) {
/* 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();
float xcen, ycen;
char str[vtr::bufsize];
auto& device_ctx = g_vpr_ctx.device();
int ipin = device_ctx.rr_nodes[inode].ptc_num();
g->set_color(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);
g->fill_rectangle({xcen - draw_coords->pin_size, ycen - draw_coords->pin_size},
{xcen + draw_coords->pin_size, ycen + draw_coords->pin_size});
sprintf(str, "%d", ipin);
g->set_color(ezgl::BLACK);
g->draw_text({xcen, ycen}, str, 2 * draw_coords->pin_size, 2 * draw_coords->pin_size);
g->set_color(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_physical_tile_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, ezgl::color color, ezgl::renderer* g) {
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();
g->set_color(color);
g->fill_rectangle({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, ezgl::renderer* g) {
/* 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();
g->set_line_dash(ezgl::line_dash::none);
/* 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] == ezgl::BLACK)
continue;
draw_routed_net(net_id, g);
} /* End for (each net) */
}
static void draw_routed_net(ClusterNetId net_id, ezgl::renderer* g) {
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, g);
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, g);
}
//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, ezgl::renderer* g) {
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();
auto 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, g);
break;
}
case IPIN: {
draw_rr_pin(inode, draw_state->draw_rr_node[inode].color, g);
if (device_ctx.rr_nodes[prev_node].type() == OPIN) {
draw_pin_to_pin(prev_node, inode, g);
} else {
draw_pin_to_chan_edge(inode, prev_node, g);
}
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, g);
switch (prev_type) {
case CHANX: {
draw_chanx_to_chanx_edge(prev_node, inode,
itrack, switch_type, g);
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, g);
break;
}
case OPIN: {
draw_pin_to_chan_edge(prev_node, inode, g);
break;
}
default: {
VPR_ERROR(VPR_ERROR_OTHER,
"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, g);
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, g);
break;
}
case CHANY: {
draw_chany_to_chany_edge(prev_node, inode,
itrack, switch_type, g);
break;
}
case OPIN: {
draw_pin_to_chan_edge(prev_node, inode, g);
break;
}
default: {
VPR_ERROR(VPR_ERROR_OTHER,
"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.
*/
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 == ezgl::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 == ezgl::WHITE) {
// If node is de-selected.
draw_state->net_color[net_id] = ezgl::BLACK;
break;
}
}
}
application.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.
*/
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 (t_edge_size 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 == ezgl::MAGENTA && draw_state->draw_rr_node[fanout_node].color != ezgl::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 == ezgl::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 (t_edge_size 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 == ezgl::MAGENTA && draw_state->draw_rr_node[inode].color != ezgl::MAGENTA) {
// If node is highlighted, highlight its fanin
draw_state->draw_rr_node[inode].color = ezgl::BLUE;
draw_state->draw_rr_node[inode].node_highlighted = true;
} else if (draw_state->draw_rr_node[node].color == ezgl::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;
ezgl::rectangle 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_physical_tile_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;
}
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;
}
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 (t_edge_size 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();
if (draw_state->draw_rr_toggle == DRAW_NO_RR && !draw_state->show_nets) {
application.update_message(draw_state->default_message);
application.refresh_drawing();
return false; //No rr shown
}
// Check which rr_node (if any) was clicked on.
int hit_node = draw_check_rr_node_hit(x, y);
return highlight_rr_nodes(hit_node);
}
# if defined(X11) && !defined(__MINGW32__)
void act_on_key_press(ezgl::application* /*app*/, GdkEventKey* /*event*/, char* key_name) {
//VTR_LOG("Key press %c (%d)\n", key_pressed, keysym);
std::string key(key_name);
}
# else
void act_on_key_press(ezgl::application* /*app*/, GdkEventKey* /*event*/, char* /*key_name*/) {
}
# endif
void act_on_mouse_press(ezgl::application* app, GdkEventButton* event, double x, double y) {
app->update_message("Mouse Clicked");
// std::cout << "User clicked the ";
if (event->button == 1) {
// std::cout << "left ";
if (window_mode) {
//click on any two points to form new window rectangle bound
if (!window_point_1_collected) {
//collect first point data
window_point_1_collected = true;
point_1 = {x, y};
} else {
//collect second point data
//click on any two points to form new window rectangle bound
ezgl::point2d point_2 = {x, y};
ezgl::rectangle current_window = (app->get_canvas(app->get_main_canvas_id()))->get_camera().get_world();
//calculate a rectangle with the same ratio based on the two clicks
double window_ratio = current_window.height() / current_window.width();
double new_height = abs(point_1.y - point_2.y);
double new_width = new_height / window_ratio;
//zoom in
ezgl::rectangle new_window = {point_1, {point_1.x + new_width, point_2.y}};
(app->get_canvas(app->get_main_canvas_id()))->get_camera().set_world(new_window);
//reset flags
window_mode = false;
window_point_1_collected = false;
}
app->refresh_drawing();
} else {
// regular clicking mode
/* 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. */
/* Control + mouse click to select multiple nets. */
if (!(event->state & GDK_CONTROL_MASK))
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(x, y)) {
return; //Selected an rr node
}
highlight_blocks(x, y);
}
}
// else if (event->button == 2)
// std::cout << "middle ";
// else if (event->button == 3)
// std::cout << "right ";
// std::cout << "mouse button at coordinates (" << x << "," << y << ") " << std::endl;
}
void act_on_mouse_move(ezgl::application* app, GdkEventButton* event, double x, double y) {
// std::cout << "Mouse move at coordinates (" << x << "," << y << ") "<< std::endl;
// user has clicked the window button, in window mode
if (window_point_1_collected) {
// draw a grey, dashed-line box to indicate the zoom-in region
app->refresh_drawing();
ezgl::renderer* g = app->get_renderer();
g->set_line_dash(ezgl::line_dash::asymmetric_5_3);
g->set_color(blk_GREY);
g->set_line_width(2);
g->draw_rectangle(point_1, {x, y});
return;
}
// user has not clicked the window button, in regular mode
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(x, 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());
app->update_message(msg.c_str());
} else {
if (!rr_highlight_message.empty()) {
app->update_message(rr_highlight_message.c_str());
} else {
app->update_message(draw_state->default_message);
}
}
}
event = event; // just for hiding warning message
}
void draw_highlight_blocks_color(t_logical_block_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 < physical_tile_type(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 = physical_tile_type(type)->pin_class[k];
if (physical_tile_type(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] = ezgl::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] = ezgl::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;
}
}
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] = ezgl::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();
draw_state->block_color[blk_id] = get_block_type_color(physical_tile_type(blk_id));
}
/**
* Draws a small triangle, 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(ezgl::renderer* g, ezgl::point2d start, ezgl::point2d 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(g, xtri, ytri, start.x, end.x, start.y, end.y, arrow_size);
}
/* Draws a triangle 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(ezgl::renderer* g, ezgl::point2d loc, ezgl::point2d start, ezgl::point2d end, float arrow_size) {
draw_triangle_along_line(g, loc.x, loc.y, start.x, end.x, start.y, end.y, arrow_size);
}
/**
* Draws a triangle 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(ezgl::renderer* g, 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;
std::vector<ezgl::point2d> poly;
xdelta = x2 - x1;
ydelta = y2 - y1;
magnitude = sqrt(xdelta * xdelta + ydelta * ydelta);
xunit = xdelta / magnitude;
yunit = ydelta / magnitude;
poly.push_back({xend + xunit * switch_rad, yend + yunit * switch_rad});
xbaseline = xend - xunit * switch_rad;
ybaseline = yend - yunit * switch_rad;
poly.push_back({xbaseline + yunit * switch_rad, ybaseline - xunit * switch_rad});
poly.push_back({xbaseline - yunit * switch_rad, ybaseline + xunit * switch_rad});
g->fill_poly(poly);
}
static void draw_pin_to_chan_edge(int pin_node, int chan_node, ezgl::renderer* g) {
/* 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 perpendicular 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_physical_tile_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);
ezgl::rectangle 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);
}
}
g->draw_line({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)) {
draw_x(x2, y2, 0.7 * draw_coords->pin_size, g);
} else {
float xend = x2 + (x1 - x2) / 10.;
float yend = y2 + (y1 - y2) / 10.;
draw_triangle_along_line(g, xend, yend, x1, x2, y1, y2);
}
}
static void draw_pin_to_pin(int opin_node, int ipin_node, ezgl::renderer* g) {
/* 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);
g->draw_line({x1, y1}, {x2, y2});
float xend = x2 + (x1 - x2) / 10.;
float yend = y2 + (y1 - y2) / 10.;
draw_triangle_along_line(g, xend, yend, x1, x2, y1, y2);
}
static inline void draw_mux_with_size(ezgl::point2d origin, e_side orientation, float height, int size, ezgl::renderer* g) {
g->set_color(ezgl::YELLOW);
auto bounds = draw_mux(origin, orientation, height, g);
g->set_color(ezgl::BLACK);
g->draw_text(bounds.center(), std::to_string(size), bounds.width(), bounds.height());
}
//Draws a mux
static inline ezgl::rectangle draw_mux(ezgl::point2d origin, e_side orientation, float height, ezgl::renderer* g) {
return draw_mux(origin, orientation, height, 0.4 * height, 0.6, g);
}
//Draws a mux, height/width define the bounding box, scale [0.,1.] controls the slope of the muxes sides
static inline ezgl::rectangle draw_mux(ezgl::point2d origin, e_side orientation, float height, float width, float scale, ezgl::renderer* g) {
std::vector<ezgl::point2d> mux_polygon;
switch (orientation) {
case TOP:
//Clock-wise from bottom left
mux_polygon.push_back({origin.x - height / 2, origin.y - width / 2});
mux_polygon.push_back({origin.x - (scale * height) / 2, origin.y + width / 2});
mux_polygon.push_back({origin.x + (scale * height) / 2, origin.y + width / 2});
mux_polygon.push_back({origin.x + height / 2, origin.y - width / 2});
break;
case BOTTOM:
//Clock-wise from bottom left
mux_polygon.push_back({origin.x - (scale * height) / 2, origin.y - width / 2});
mux_polygon.push_back({origin.x - height / 2, origin.y + width / 2});
mux_polygon.push_back({origin.x + height / 2, origin.y + width / 2});
mux_polygon.push_back({origin.x + (scale * height) / 2, origin.y - width / 2});
break;
case LEFT:
//Clock-wise from bottom left
mux_polygon.push_back({origin.x - width / 2, origin.y - (scale * height) / 2});
mux_polygon.push_back({origin.x - width / 2, origin.y + (scale * height) / 2});
mux_polygon.push_back({origin.x + width / 2, origin.y + height / 2});
mux_polygon.push_back({origin.x + width / 2, origin.y - height / 2});
break;
case RIGHT:
//Clock-wise from bottom left
mux_polygon.push_back({origin.x - width / 2, origin.y - height / 2});
mux_polygon.push_back({origin.x - width / 2, origin.y + height / 2});
mux_polygon.push_back({origin.x + width / 2, origin.y + (scale * height) / 2});
mux_polygon.push_back({origin.x + width / 2, origin.y - (scale * height) / 2});
break;
default:
VTR_ASSERT_MSG(false, "Unrecognized orientation");
}
g->fill_poly(mux_polygon);
ezgl::point2d min((float)mux_polygon[0].x, (float)mux_polygon[0].y);
ezgl::point2d max((float)mux_polygon[0].x, (float)mux_polygon[0].y);
for (const auto& point : mux_polygon) {
min.x = std::min((float)min.x, (float)point.x);
min.y = std::min((float)min.y, (float)point.y);
max.x = std::max((float)max.x, (float)point.x);
max.y = std::max((float)max.y, (float)point.y);
}
return ezgl::rectangle(min, max);
}
ezgl::point2d 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);
}
ezgl::point2d 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();
ezgl::rectangle 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.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 ezgl::point2d point = {
x_offset + usable_width * pin_index / ((float)pin_total),
pb_bbox.center_y()};
return point;
}
static void draw_crit_path(ezgl::renderer* g) {
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
ezgl::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) {
g->set_color(color);
g->set_line_dash(ezgl::line_dash::none);
draw_flyline_timing_edge(tnode_draw_coord(prev_node), tnode_draw_coord(node), delay, g);
} 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, g);
}
}
prev_node = node;
prev_arr_time = arr_time;
}
}
static void draw_flyline_timing_edge(ezgl::point2d start, ezgl::point2d end, float incr_delay, ezgl::renderer* g) {
g->draw_line(start, end);
draw_triangle_along_line(g, start, end, 0.95, 40 * DEFAULT_ARROW_SIZE);
draw_triangle_along_line(g, 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
ezgl::rectangle 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();
g->draw_text(text_bbox.center(), incr_delay_str.c_str(), text_bbox.width(), text_bbox.height());
}
}
static void draw_routed_timing_edge(tatum::NodeId start_tnode, tatum::NodeId end_tnode, float incr_delay, ezgl::color color, ezgl::renderer* g) {
draw_routed_timing_edge_connection(start_tnode, end_tnode, color, g);
g->set_line_dash(ezgl::line_dash::asymmetric_5_3);
g->set_line_width(3);
g->set_color(color);
draw_flyline_timing_edge((ezgl::point2d)tnode_draw_coord(start_tnode), (ezgl::point2d)tnode_draw_coord(end_tnode), (float)incr_delay, (ezgl::renderer*)g);
g->set_line_width(0);
g->set_line_dash(ezgl::line_dash::none);
}
//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, ezgl::color color, ezgl::renderer* g) {
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<ezgl::point2d> 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);
std::vector<int> 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((std::vector<int>)routed_rr_nodes, (ezgl::renderer*)g);
} 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());
free_route_tree(rt_root);
if (allocated_route_tree_structs) {
free_route_tree_timing_structs();
}
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 t_edge_size find_edge(int prev_inode, int inode) {
auto& device_ctx = g_vpr_ctx.device();
for (t_edge_size 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;
}
ezgl::color to_ezgl_color(vtr::Color<float> color) {
return ezgl::color(color.r * 255, color.g * 255, color.b * 255);
}
static void draw_color_map_legend(const vtr::ColorMap& cmap, ezgl::renderer* g) {
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;
g->set_coordinate_system(ezgl::SCREEN);
float screen_width = application.get_canvas(application.get_main_canvas_id())->width();
float screen_height = application.get_canvas(application.get_main_canvas_id())->height();
float vert_offset = screen_height * LEGEND_VERT_OFFSET_FAC;
float legend_width = std::min<int>(LEGEND_WIDTH_FAC * screen_width, 100);
// In SCREEN coordinate: bottom_left is (0,0), right_top is (screen_width, screen_height)
ezgl::rectangle legend({0, vert_offset}, {legend_width, screen_height - vert_offset});
float range = cmap.max() - cmap.min();
float height_incr = legend.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;
ezgl::color color = to_ezgl_color(cmap.color(val));
g->set_color(color);
g->fill_rectangle({legend.left(), legend.top() - i * height_incr},
{legend.right(), legend.top() - (i + 1) * height_incr});
}
//Min mark
g->set_color(blk_SKYBLUE); // set to skyblue so its easier to see
std::string str = vtr::string_fmt("%.3g", cmap.min());
g->draw_text({legend.center_x(), legend.top() - TEXT_OFFSET}, str.c_str());
//Mid marker
g->set_color(ezgl::BLACK);
str = vtr::string_fmt("%.3g", cmap.min() + (cmap.range() / 2.));
g->draw_text({legend.center_x(), legend.center_y()}, str.c_str());
//Max marker
g->set_color(ezgl::BLACK);
str = vtr::string_fmt("%.3g", cmap.max());
g->draw_text({legend.center_x(), legend.bottom() + TEXT_OFFSET}, str.c_str());
g->set_color(ezgl::BLACK);
g->draw_rectangle(legend);
g->set_coordinate_system(ezgl::WORLD);
}
ezgl::color get_block_type_color(t_physical_tile_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
ezgl::color color = block_colors[type->index % block_colors.size()];
return color;
}
//Lightens a color's luminance [0, 1] by an aboslute 'amount'
ezgl::color lighten_color(ezgl::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_physical_tile_type_ptr, size_t> total_input_pins;
std::map<t_physical_tile_type_ptr, size_t> total_output_pins;
for (const auto& type : device_ctx.physical_tile_types) {
if (is_empty_type(&type)) {
continue;
}
t_pb_type* pb_type = logical_block_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 = physical_tile_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) {
ezgl::color color = to_ezgl_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) {
application.update_message("Block Total Pin Utilization");
} else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_INPUTS) {
application.update_message("Block Input Pin Utilization");
} else if (draw_state->show_blk_pin_util == DRAW_BLOCK_PIN_UTIL_OUTPUTS) {
application.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(ezgl::renderer* g) {
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]);
ezgl::color chanx_color = to_ezgl_color(cmap->color(chanx_util));
chanx_color.alpha *= ALPHA;
g->set_color(chanx_color);
ezgl::rectangle 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]});
g->fill_rectangle(bb);
g->set_color(ezgl::BLACK);
if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE) {
g->draw_text(bb.center(), vtr::string_fmt("%.2f", chanx_util).c_str(), bb.width(), bb.height());
} else if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) {
g->draw_text(bb.center(), vtr::string_fmt("%.2f = %.0f / %.0f", chanx_util, chanx_usage[x][y], chanx_avail[x][y]).c_str(), bb.width(), bb.height());
}
sb_util += chanx_util;
++chan_count;
}
if (y > 0) {
chany_util = routing_util(chany_usage[x][y], chany_avail[x][y]);
ezgl::color chany_color = to_ezgl_color(cmap->color(chany_util));
chany_color.alpha *= ALPHA;
g->set_color(chany_color);
ezgl::rectangle 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});
g->fill_rectangle(bb);
g->set_color(ezgl::BLACK);
if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE) {
g->draw_text(bb.center(), vtr::string_fmt("%.2f", chany_util).c_str(), bb.width(), bb.height());
} else if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) {
g->draw_text(bb.center(), vtr::string_fmt("%.2f = %.0f / %.0f", chany_util, chany_usage[x][y], chany_avail[x][y]).c_str(), bb.width(), bb.height());
}
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;
ezgl::color sb_color = to_ezgl_color(cmap->color(sb_util));
sb_color.alpha *= ALPHA;
g->set_color(sb_color);
ezgl::rectangle 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]});
g->fill_rectangle(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) {
ezgl::rectangle 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});
g->fill_rectangle(bb2);
}
}
g->set_color(ezgl::BLACK);
if (draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_VALUE
|| draw_state->show_routing_util == DRAW_ROUTING_UTIL_WITH_FORMULA) {
g->draw_text(bb.center(), vtr::string_fmt("%.2f", sb_util).c_str(), bb.width(), bb.height());
}
}
}
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(ezgl::renderer* g) {
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(g, rr_costs, false);
}
if (draw_state->show_router_rr_cost == DRAW_ROUTER_RR_COST_TOTAL) {
application.update_message("Routing Expected Total Cost (known + estimate)");
} else if (draw_state->show_router_rr_cost == DRAW_ROUTER_RR_COST_KNOWN) {
application.update_message("Routing Known Cost (from source to node)");
} else {
application.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(ezgl::renderer* g, 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();
g->set_line_width(0);
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;
ezgl::color color = to_ezgl_color(cmap->color(cost));
switch (device_ctx.rr_nodes[inode].type()) {
case CHANX: //fallthrough
case CHANY:
draw_rr_chan(inode, color, g);
draw_rr_edges(inode, g);
break;
case IPIN: //fallthrough
draw_rr_pin(inode, color, g);
break;
case OPIN:
draw_rr_pin(inode, color, g);
draw_rr_edges(inode, g);
break;
case SOURCE:
case SINK:
color.alpha *= 0.8;
draw_rr_src_sink(inode, color, g);
break;
default:
break;
}
}
draw_state->color_map = std::move(cmap);
}
static void draw_placement_macros(ezgl::renderer* g) {
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();
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 + physical_tile_type(blk)->width);
yhigh = std::max(yhigh, y + physical_tile_type(blk)->height);
}
double draw_xlow = draw_coords->tile_x[xlow];
double draw_ylow = draw_coords->tile_y[ylow];
double draw_xhigh = draw_coords->tile_x[xhigh];
double draw_yhigh = draw_coords->tile_y[yhigh];
g->set_color(blk_RED);
g->draw_rectangle({draw_xlow, draw_ylow}, {draw_xhigh, draw_yhigh});
ezgl::color fill = blk_SKYBLUE;
fill.alpha *= 0.3;
g->set_color(fill);
g->fill_rectangle({draw_xlow, draw_ylow}, {draw_xhigh, draw_yhigh});
}
}
static void highlight_blocks(double x, double y) {
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();
/// determine block ///
ezgl::rectangle clb_bbox;
// iterate over grid x
for (size_t i = 0; i < device_ctx.grid.width(); ++i) {
if (draw_coords->tile_x[i] > 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] > 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.contains({x, 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
ezgl::point2d point_in_clb = ezgl::point2d(x, 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);
}
application.update_message(msg);
application.refresh_drawing();
}
void setup_default_ezgl_callbacks(ezgl::application* app) {
// Connect press_proceed function to the Proceed button
GObject* proceed_button = app->get_object("ProceedButton");
g_signal_connect(proceed_button, "clicked", G_CALLBACK(ezgl::press_proceed), app);
// Connect press_zoom_fit function to the Zoom-fit button
GObject* zoom_fit_button = app->get_object("ZoomFitButton");
g_signal_connect(zoom_fit_button, "clicked", G_CALLBACK(ezgl::press_zoom_fit), app);
// Connect Pause button
GObject* pause_button = app->get_object("PauseButton");
g_signal_connect(pause_button, "clicked", G_CALLBACK(set_force_pause), app);
}
void set_force_pause(GtkWidget* /*widget*/, gint /*response_id*/, gpointer /*data*/) {
t_draw_state* draw_state = get_draw_state_vars();
draw_state->forced_pause = true;
}
#endif /* NO_GRAPHICS */