#include #include #include #include #include "util.h" #include "vpr_types.h" #include "globals.h" #include "path_delay.h" #include "path_delay2.h" #include "net_delay.h" #include "vpr_utils.h" #include #include "read_xml_arch_file.h" #include "ReadOptions.h" #include "read_sdc.h" #include "stats.h" /**************************** Top-level summary ****************************** Timing analysis by Vaughn Betz, Jason Luu, and Michael Wainberg. Timing analysis is a three-step process: 1. Interpret the constraints specified by the user in an SDC (Synopsys Design Constraints) file or, if none is specified, use default constraints. 2. Convert the pre- or post-packed netlist (depending on the stage of the flow) into a timing graph, where nodes represent pins and edges represent dependencies and delays between pins (see "Timing graph structure", below). 3. Traverse the timing graph to obtain information about which connections to optimize, as well as statistics for the user. Steps 1 and 2 are performed through one of two helper functions: alloc_and_load_timing_graph and alloc_and_load_pre_packing_timing_graph. The first step is to create the timing graph, which is stored in the array tnode ("timing node"). This is done through alloc_and_load_tnodes (post- packed) or alloc_and_load_tnodes_from_prepacked_netlist. Then, the timing graph is topologically sorted ("levelized") in alloc_and_load_timing_graph_levels, to allow for faster traversals later. read_sdc reads the SDC file, interprets its contents and stores them in the data structure g_sdc. (This data structure does not need to remain global but it is probably easier, since its information is used in both netlists and only needs to be read in once.) load_clock_domain_and_clock_and_io_delay then gives each flip-flop and I/O the index of a constrained clock from the SDC file in g_sdc->constrained_ clocks, or -1 if an I/O is unconstrained. process_constraints does a pre-traversal through the timing graph and prunes all constraints between domains that never intersect so they are not analysed. Step 3 is performed through do_timing_analysis. For each constraint between a pair of clock domains we do a forward traversal from the "source" domain to the "sink" domain to compute each tnode's arrival time, the time when the latest signal would arrive at the node. We also do a backward traversal to compute required time, the time when the earliest signal has to leave the node to meet the constraint. The "slack" of each sink pin on each net is basically the difference between the two times. If path counting is on, we calculate a forward and backward path weight in do_path_counting. These represent the importance of paths fanning, respectively, into and out of this pin, in such a way that paths with a higher slack are discounted exponentially in importance. If path counting is off and we are using the pre-packed netlist, we also calculate normalized costs for the clusterer (normalized arrival time, slack and number of critical paths) in normalized_costs. The clusterer uses these to calculate a criticality for each block. Finally, in update_slacks, we calculate the slack for each sink pin on each net for printing, as well as a derived metric, timing criticality, which the optimizers actually use. If path counting is on, we calculate a path criticality from the forward and backward weights on each tnode. */ /************************* Timing graph structure **************************** Author: V. Betz We can build timing graphs that match either the primitive (logical_block) netlist (of basic elements before clustering, like FFs and LUTs) or that match the clustered netlist (block). You pass in the is_pre_packed flag to say which kind of netlist and timing graph you are working with. Every used (not OPEN) block pin becomes a timing node, both on primitive blocks and (if you’re building the timing graph that matches a clustered netlist) on clustered blocks. For the clustered (not pre_packed) timing graph, every used pin within a clustered (pb_type) block also becomes a timing node. So a CLB that contains ALMs that contains LUTs will have nodes created for the CLB pins, ALM pins and LUT pins, with edges that connect them as specified in the clustered netlist. Unused (OPEN) pins don't create any timing nodes. The primitive blocks have edges from the tnodes that represent input pins and output pins that represent the timing dependencies within these lowest level blocks (e.g. LUTs and FFs and IOs). The exact edges created come from reading the architecture file. A LUT for example will have delays specified from all its inputs to its output, and hence we will create a tedge from each tnode corresponding to an input pin of the LUT to the tnode that represents the LUT output pin. The delay marked on each edge is read from the architecture file. A FF has nodes representing its pins, but also two extra nodes, representing the TN_FF_SINK and TN_FF_SOURCE. Those two nodes represent the internal storage node of the FF. The TN_FF_SINK has edges going to it from the regular FF inputs (but not the clock input), with appropriate delays. It has no outgoing edges, since it terminates timing paths. The TN_FF_SOURCE has an edge going from it to the FF output pin, with the appropriate delay. TN_FF_SOURCES have no edges; they start timing paths. FF clock pins have no outgoing edges, but the delay to them can be looked up, and is used in the timing traversals to compute clock delay for slack computations. In the timing graph I create, input pads and constant generators have no inputs (incoming edges), just like TN_FF_SOURCES. Every input pad and output pad is represented by two tnodes -- an input pin/source and an output pin/ sink. For an input pad the input source comes from off chip and has no fanin, while the output pin drives signals within the chip. For output pads, the input pin is driven by signal (net) within the chip, and the output sink node goes off chip and has no fanout (out-edges). I need two nodes to respresent things like pads because I mark all delay on tedges, not on tnodes. One other subblock that needs special attention is a constant generator. This has no used inputs, but its output is used. I create an extra tnode, a dummy input, in addition to the output pin tnode. The dummy tnode has no fanin. Since constant generators really generate their outputs at T = -infinity, I set the delay from the input tnode to the output to a large- magnitude negative number. This guarantees every block that needs the output of a constant generator sees it available very early. The main structure of the timing graph is given by the nodes and the edges that connect them. We also store some extra information on tnodes that (1) lets us figure out how to map from a tnode back to the netlist pin (or other item) it represents and (2) figure out what clock domain it is on, if it is a timing path start point (no incoming edges) or end point (no outgoing edges) and (3) lets us figure out the delay of the clock to that node, if it is a timing path start or end point. To map efficiently from tedges back to the netlist pins, we create the tedge array driven by a tnode the represents a netlist output pin *in the same order as the netlist net pins*. That means the edge index for the tedge array from such a tnode guarantees iedge = net_pin_index 1. The code to map slacks from the timing graph back to the netlist relies on this. */ /*************************** Global variables *******************************/ t_tnode *tnode = NULL; /* [0..num_tnodes - 1] */ int num_tnodes = 0; /* Number of nodes (pins) in the timing graph */ /******************** Variables local to this module ************************/ #define NUM_BUCKETS 5 /* Used when printing slack and criticality. */ /* Variables for "chunking" the tedge memory. If the head pointer in tedge_ch is NULL, * * no timing graph exists now. */ static t_chunk tedge_ch = {NULL, 0, NULL}; static struct s_net *timing_nets = NULL; static int num_timing_nets = 0; static t_timing_stats * f_timing_stats = NULL; /* Critical path delay and worst-case slack per constraint. */ static int * f_net_to_driver_tnode; /* [0..num_nets - 1]. Gives the index of the tnode that drives each net. Used for both pre- and post-packed netlists. If you just want the number of edges on the driver tnode, use: num_edges = timing_nets[inet].num_sinks; instead of the equivalent but more convoluted: driver_tnode = f_net_to_driver_tnode[inet]; num_edges = tnode[driver_tnode].num_edges; Only use this array if you want the actual edges themselves or the index of the driver tnode. */ /***************** Subroutines local to this module *************************/ static t_slack * alloc_slacks(void); static void update_slacks(t_slack * slacks, int source_clock_domain, int sink_clock_domain, float criticality_denom, boolean update_slack); static void alloc_and_load_tnodes(t_timing_inf timing_inf); static void alloc_and_load_tnodes_from_prepacked_netlist(float block_delay, float inter_cluster_net_delay); static void alloc_timing_stats(void); static float do_timing_analysis_for_constraint(int source_clock_domain, int sink_clock_domain, boolean is_prepacked, boolean is_final_analysis, long * max_critical_input_paths_ptr, long * max_critical_output_paths_ptr); #ifdef PATH_COUNTING static void do_path_counting(float criticality_denom); #endif static void do_lut_rebalancing(); static void load_tnode(INP t_pb_graph_pin *pb_graph_pin, INP int iblock, INOUTP int *inode, INP t_timing_inf timing_inf); #ifndef PATH_COUNTING static void update_normalized_costs(float T_arr_max_this_domain, long max_critical_input_paths, long max_critical_output_paths); #endif static void print_primitive_as_blif (FILE *fpout, int iblk); static void set_and_balance_arrival_time(int to_node, int from_node, float Tdel, boolean do_lut_input_balancing); static void load_clock_domain_and_clock_and_io_delay(boolean is_prepacked); static char * find_tnode_net_name(int inode, boolean is_prepacked); static t_tnode * find_ff_clock_tnode(int inode, boolean is_prepacked); static inline int get_tnode_index(t_tnode * node); static inline boolean has_valid_T_arr(int inode); static inline boolean has_valid_T_req(int inode); static int find_clock(char * net_name); static int find_input(char * net_name); static int find_output(char * net_name); static int find_cf_constraint(char * source_clock_name, char * sink_ff_name); static void propagate_clock_domain_and_skew(int inode); static void process_constraints(void); static void print_global_criticality_stats(FILE * fp, float ** criticality, const char * singular_name, const char * capitalized_plural_name); static void print_timing_constraint_info(const char *fname); static void print_spaces(FILE * fp, int num_spaces); /********************* Subroutine definitions *******************************/ t_slack * alloc_and_load_timing_graph(t_timing_inf timing_inf) { /* This routine builds the graph used for timing analysis. Every cb pin is a * timing node (tnode). The connectivity between pins is * * represented by timing edges (tedges). All delay is marked on edges, not * * on nodes. Returns two arrays that will store slack values: * * slack and criticality ([0..num_nets-1][1..num_pins]). */ /* For pads, only the first two pin locations are used (input to pad is first, * output of pad is second). For CLBs, all OPEN pins on the cb have their * mapping set to OPEN so I won't use it by mistake. */ int num_sinks; t_slack * slacks = NULL; boolean do_process_constraints = FALSE; if (tedge_ch.chunk_ptr_head != NULL) { vpr_printf(TIO_MESSAGE_ERROR, "in alloc_and_load_timing_graph: An old timing graph still exists.\n"); exit(1); } num_timing_nets = num_nets; timing_nets = clb_net; alloc_and_load_tnodes(timing_inf); num_sinks = alloc_and_load_timing_graph_levels(); check_timing_graph(num_sinks); slacks = alloc_slacks(); if (g_sdc == NULL) { /* the SDC timing constraints only need to be read in once; * * if they haven't been already, do it now */ read_sdc(timing_inf); do_process_constraints = TRUE; } load_clock_domain_and_clock_and_io_delay(FALSE); if (do_process_constraints) process_constraints(); if (f_timing_stats == NULL) alloc_timing_stats(); return slacks; } t_slack * alloc_and_load_pre_packing_timing_graph(float block_delay, float inter_cluster_net_delay, t_model *models, t_timing_inf timing_inf) { /* This routine builds the graph used for timing analysis. Every technology- * mapped netlist pin is a timing node (tnode). The connectivity between pins is * * represented by timing edges (tedges). All delay is marked on edges, not * * on nodes. Returns two arrays that will store slack values: * * slack and criticality ([0..num_nets-1][1..num_pins]). */ /* For pads, only the first two pin locations are used (input to pad is first, * output of pad is second). For CLBs, all OPEN pins on the cb have their * mapping set to OPEN so I won't use it by mistake. */ int num_sinks; t_slack * slacks = NULL; boolean do_process_constraints = FALSE; if (tedge_ch.chunk_ptr_head != NULL) { vpr_printf(TIO_MESSAGE_ERROR, "in alloc_and_load_timing_graph: An old timing graph still exists.\n"); exit(1); } num_timing_nets = num_logical_nets; timing_nets = vpack_net; alloc_and_load_tnodes_from_prepacked_netlist(block_delay, inter_cluster_net_delay); num_sinks = alloc_and_load_timing_graph_levels(); slacks = alloc_slacks(); check_timing_graph(num_sinks); if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_PRE_PACKING_TIMING_GRAPH_AS_BLIF)) { print_timing_graph_as_blif(getEchoFileName(E_ECHO_PRE_PACKING_TIMING_GRAPH_AS_BLIF), models); } if (g_sdc == NULL) { /* the SDC timing constraints only need to be read in once; * * if they haven't been already, do it now */ read_sdc(timing_inf); do_process_constraints = TRUE; } load_clock_domain_and_clock_and_io_delay(TRUE); if (do_process_constraints) process_constraints(); if (f_timing_stats == NULL) alloc_timing_stats(); return slacks; } static t_slack * alloc_slacks(void) { /* Allocates the slack, criticality and path_criticality structures ([0..num_nets-1][1..num_pins-1]). Chunk allocated to save space. */ int inet; t_slack * slacks = (t_slack *) my_malloc(sizeof(t_slack)); slacks->slack = (float **) my_malloc(num_timing_nets * sizeof(float *)); slacks->timing_criticality = (float **) my_malloc(num_timing_nets * sizeof(float *)); #ifdef PATH_COUNTING slacks->path_criticality = (float **) my_malloc(num_timing_nets * sizeof(float *)); #endif for (inet = 0; inet < num_timing_nets; inet++) { slacks->slack[inet] = (float *) my_chunk_malloc((timing_nets[inet].num_sinks + 1) * sizeof(float), &tedge_ch); slacks->timing_criticality[inet] = (float *) my_chunk_malloc((timing_nets[inet].num_sinks + 1) * sizeof(float), &tedge_ch); #ifdef PATH_COUNTING slacks->path_criticality[inet] = (float *) my_chunk_malloc((timing_nets[inet].num_sinks + 1) * sizeof(float), &tedge_ch); #endif } return slacks; } void load_timing_graph_net_delays(float **net_delay) { /* Sets the delays of the inter-CLB nets to the values specified by * * net_delay[0..num_nets-1][1..num_pins-1]. These net delays should have * * been allocated and loaded with the net_delay routines. This routine * * marks the corresponding edges in the timing graph with the proper delay. */ int inet, ipin, inode; t_tedge *tedge; for (inet = 0; inet < num_timing_nets; inet++) { inode = f_net_to_driver_tnode[inet]; tedge = tnode[inode].out_edges; /* Note that the edges of a tnode corresponding to a CLB or INPAD opin must * * be in the same order as the pins of the net driven by the tnode. */ for (ipin = 1; ipin < (timing_nets[inet].num_sinks + 1); ipin++) tedge[ipin - 1].Tdel = net_delay[inet][ipin]; } } void free_timing_graph(t_slack * slacks) { int inode; if (tedge_ch.chunk_ptr_head == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "in free_timing_graph: No timing graph to free.\n"); exit(1); } free_chunk_memory(&tedge_ch); if (tnode[0].prepacked_data) { /* If we allocated prepacked_data for the first node, it must be allocated for all other nodes too. */ for (inode = 0; inode < num_tnodes; inode++) { free(tnode[inode].prepacked_data); } } free(tnode); free(f_net_to_driver_tnode); free_ivec_vector(tnodes_at_level, 0, num_tnode_levels - 1); free(slacks->slack); free(slacks->timing_criticality); #ifdef PATH_COUNTING free(slacks->path_criticality); #endif free(slacks); tnode = NULL; num_tnodes = 0; f_net_to_driver_tnode = NULL; tnodes_at_level = NULL; num_tnode_levels = 0; slacks = NULL; } void free_timing_stats(void) { int i; if(f_timing_stats != NULL) { for (i = 0; i < g_sdc->num_constrained_clocks; i++) { free(f_timing_stats->cpd[i]); free(f_timing_stats->least_slack[i]); } free(f_timing_stats->cpd); free(f_timing_stats->least_slack); free(f_timing_stats); } f_timing_stats = NULL; } void print_slack(float ** slack, boolean slack_is_normalized, const char *fname) { /* Prints slacks into a file. */ int inet, iedge, ibucket, driver_tnode, num_edges, num_unused_slacks = 0; t_tedge * tedge; FILE *fp; float max_slack = HUGE_NEGATIVE_FLOAT, min_slack = HUGE_POSITIVE_FLOAT, total_slack = 0, total_negative_slack = 0, bucket_size, slk; int slacks_in_bucket[NUM_BUCKETS]; fp = my_fopen(fname, "w", 0); if (slack_is_normalized) { fprintf(fp, "The following slacks have been normalized to be non-negative by " "relaxing the required times to the maximum arrival time.\n\n"); } /* Go through slack once to get the largest and smallest slack, both for reporting and so that we can delimit the buckets. Also calculate the total negative slack in the design. */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { slk = slack[inet][iedge + 1]; if (slk < HUGE_POSITIVE_FLOAT - 1) { /* if slack was analysed */ max_slack = std::max(max_slack, slk); min_slack = std::min(min_slack, slk); total_slack += slk; if (slk < NEGATIVE_EPSILON) { total_negative_slack -= slk; /* By convention, we'll have total_negative_slack be a positive number. */ } } else { /* slack was never analysed */ num_unused_slacks++; } } } if (max_slack > HUGE_NEGATIVE_FLOAT + 1) { fprintf(fp, "Largest slack in design: %g\n", max_slack); } else { fprintf(fp, "Largest slack in design: --\n"); } if (min_slack < HUGE_POSITIVE_FLOAT - 1) { fprintf(fp, "Smallest slack in design: %g\n", min_slack); } else { fprintf(fp, "Smallest slack in design: --\n"); } fprintf(fp, "Total slack in design: %g\n", total_slack); fprintf(fp, "Total negative slack: %g\n", total_negative_slack); if (max_slack - min_slack > EPSILON) { /* Only sort the slacks into buckets if not all slacks are the same (if they are identical, no need to sort). */ /* Initialize slacks_in_bucket, an array counting how many slacks are within certain linearly-spaced ranges (buckets). */ for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { slacks_in_bucket[ibucket] = 0; } /* The size of each bucket is the range of slacks, divided by the number of buckets. */ bucket_size = (max_slack - min_slack)/NUM_BUCKETS; /* Now, pass through again, incrementing the number of slacks in the nth bucket for each slack between (min_slack + n*bucket_size) and (min_slack + (n+1)*bucket_size). */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { slk = slack[inet][iedge + 1]; if (slk < HUGE_POSITIVE_FLOAT - 1) { /* We have to watch out for the special case where slack = max_slack, in which case ibucket = NUM_BUCKETS and we go out of bounds of the array. */ ibucket = std::min(NUM_BUCKETS - 1, (int) ((slk - min_slack)/bucket_size)); assert(ibucket >= 0 && ibucket < NUM_BUCKETS); slacks_in_bucket[ibucket]++; } } } /* Now print how many slacks are in each bucket. */ fprintf(fp, "\n\nRange\t\t"); for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { fprintf(fp, "%.1e to ", min_slack); min_slack += bucket_size; fprintf(fp, "%.1e\t", min_slack); } fprintf(fp, "Not analysed"); fprintf(fp, "\nSlacks in range\t\t"); for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { fprintf(fp, "%d\t\t\t", slacks_in_bucket[ibucket]); } fprintf(fp, "%d", num_unused_slacks); } /* Finally, print all the slacks, organized by net. */ fprintf(fp, "\n\nNet #\tDriver_tnode\tto_node\tSlack\n\n"); for (inet = 0; inet < num_timing_nets; inet++) { driver_tnode = f_net_to_driver_tnode[inet]; num_edges = tnode[driver_tnode].num_edges; tedge = tnode[driver_tnode].out_edges; slk = slack[inet][1]; if (slk < HUGE_POSITIVE_FLOAT - 1) { fprintf(fp, "%5d\t%5d\t\t%5d\t%g\n", inet, driver_tnode, tedge[0].to_node, slk); } else { /* Slack is meaningless, so replace with --. */ fprintf(fp, "%5d\t%5d\t\t%5d\t--\n", inet, driver_tnode, tedge[0].to_node); } for (iedge = 1; iedge < num_edges; iedge++) { /* newline and indent subsequent edges after the first */ slk = slack[inet][iedge+1]; if (slk < HUGE_POSITIVE_FLOAT - 1) { fprintf(fp, "\t\t\t%5d\t%g\n", tedge[iedge].to_node, slk); } else { /* Slack is meaningless, so replace with --. */ fprintf(fp, "\t\t\t%5d\t--\n", tedge[iedge].to_node); } } } fclose(fp); } void print_criticality(t_slack * slacks, boolean criticality_is_normalized, const char *fname) { /* Prints timing criticalities (and path criticalities if enabled) into a file. */ int inet, iedge, driver_tnode, num_edges; t_tedge * tedge; FILE *fp; fp = my_fopen(fname, "w", 0); if (criticality_is_normalized) { fprintf(fp, "Timing criticalities have been normalized to be non-negative by " "relaxing the required times to the maximum arrival time.\n\n"); } print_global_criticality_stats(fp, slacks->timing_criticality, "timing criticality", "Timing criticalities"); #ifdef PATH_COUNTING print_global_criticality_stats(fp, slacks->path_criticality, "path criticality", "Path criticalities"); #endif /* Finally, print all the criticalities, organized by net. */ fprintf(fp, "\n\nNet #\tDriver_tnode\t to_node\tTiming criticality" #ifdef PATH_COUNTING "\tPath criticality" #endif "\n"); for (inet = 0; inet < num_timing_nets; inet++) { driver_tnode = f_net_to_driver_tnode[inet]; num_edges = tnode[driver_tnode].num_edges; tedge = tnode[driver_tnode].out_edges; fprintf(fp, "\n%5d\t%5d\t\t%5d\t\t%.6f", inet, driver_tnode, tedge[0].to_node, slacks->timing_criticality[inet][1]); #ifdef PATH_COUNTING fprintf(fp, "\t\t%g", slacks->path_criticality[inet][1]); #endif for (iedge = 1; iedge < num_edges; iedge++) { /* newline and indent subsequent edges after the first */ fprintf(fp, "\n\t\t\t%5d\t\t%.6f", tedge[iedge].to_node, slacks->timing_criticality[inet][iedge+1]); #ifdef PATH_COUNTING fprintf(fp, "\t\t%g", slacks->path_criticality[inet][iedge+1]); #endif } } fclose(fp); } static void print_global_criticality_stats(FILE * fp, float ** criticality, const char * singular_name, const char * capitalized_plural_name) { /* Prints global stats for timing or path criticality to the file pointed to by fp, including maximum criticality, minimum criticality, total criticality in the design, and the number of criticalities within various ranges, or buckets. */ int inet, iedge, num_edges, ibucket, criticalities_in_bucket[NUM_BUCKETS]; float crit, max_criticality = HUGE_NEGATIVE_FLOAT, min_criticality = HUGE_POSITIVE_FLOAT, total_criticality = 0, bucket_size; /* Go through criticality once to get the largest and smallest timing criticality, both for reporting and so that we can delimit the buckets. */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { crit = criticality[inet][iedge + 1]; max_criticality = std::max(max_criticality, crit); min_criticality = std::min(min_criticality, crit); total_criticality += crit; } } fprintf(fp, "Largest %s in design: %g\n", singular_name, max_criticality); fprintf(fp, "Smallest %s in design: %g\n", singular_name, min_criticality); fprintf(fp, "Total %s in design: %g\n", singular_name, total_criticality); if (max_criticality - min_criticality > EPSILON) { /* Only sort the criticalities into buckets if not all criticalities are the same (if they are identical, no need to sort). */ /* Initialize criticalities_in_bucket, an array counting how many criticalities are within certain linearly-spaced ranges (buckets). */ for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { criticalities_in_bucket[ibucket] = 0; } /* The size of each bucket is the range of criticalities, divided by the number of buckets. */ bucket_size = (max_criticality - min_criticality)/NUM_BUCKETS; /* Now, pass through again, incrementing the number of criticalities in the nth bucket for each criticality between (min_criticality + n*bucket_size) and (min_criticality + (n+1)*bucket_size). */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { crit = criticality[inet][iedge + 1]; /* We have to watch out for the special case where criticality = max_criticality, in which case ibucket = NUM_BUCKETS and we go out of bounds of the array. */ ibucket = std::min(NUM_BUCKETS - 1, (int) ((crit - min_criticality)/bucket_size)); assert(ibucket >= 0 && ibucket < NUM_BUCKETS); criticalities_in_bucket[ibucket]++; } } /* Now print how many criticalities are in each bucket. */ fprintf(fp, "\nRange\t\t"); for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { fprintf(fp, "%.1e to ", min_criticality); min_criticality += bucket_size; fprintf(fp, "%.1e\t", min_criticality); } fprintf(fp, "\n%s in range\t\t", capitalized_plural_name); for (ibucket = 0; ibucket < NUM_BUCKETS; ibucket++) { fprintf(fp, "%d\t\t\t", criticalities_in_bucket[ibucket]); } } fprintf(fp, "\n\n"); } void print_net_delay(float **net_delay, const char *fname) { /* Prints the net delays into a file. */ int inet, iedge, driver_tnode, num_edges; t_tedge * tedge; FILE *fp; fp = my_fopen(fname, "w", 0); fprintf(fp, "Net #\tDriver_tnode\tto_node\tDelay\n\n"); for (inet = 0; inet < num_timing_nets; inet++) { driver_tnode = f_net_to_driver_tnode[inet]; num_edges = tnode[driver_tnode].num_edges; tedge = tnode[driver_tnode].out_edges; fprintf(fp, "%5d\t%5d\t\t%5d\t%g\n", inet, driver_tnode, tedge[0].to_node, net_delay[inet][1]); for (iedge = 1; iedge < num_edges; iedge++) { /* newline and indent subsequent edges after the first */ fprintf(fp, "\t\t\t%5d\t%g\n", tedge[iedge].to_node, net_delay[inet][iedge+1]); } } fclose(fp); } #ifndef PATH_COUNTING void print_clustering_timing_info(const char *fname) { /* Print information from tnodes which is used by the clusterer. */ int inode; FILE *fp; fp = my_fopen(fname, "w", 0); fprintf(fp, "inode "); if (g_sdc->num_constrained_clocks <= 1) { /* These values are from the last constraint analysed, so they're not meaningful unless there was only one constraint. */ fprintf(fp, "Critical input paths Critical output paths "); } fprintf(fp, "Normalized slack Normalized Tarr Normalized total crit paths\n"); for (inode = 0; inode < num_tnodes; inode++) { fprintf(fp, "%d\t", inode); /* Only print normalized values for tnodes which have valid normalized values. (If normalized_T_arr is valid, the others will be too.) */ if (has_valid_normalized_T_arr(inode)) { if (g_sdc->num_constrained_clocks <= 1) { fprintf(fp, "%ld\t\t\t%ld\t\t\t", tnode[inode].prepacked_data->num_critical_input_paths, tnode[inode].prepacked_data->num_critical_output_paths); } fprintf(fp, "%f\t%f\t%f\n", tnode[inode].prepacked_data->normalized_slack, tnode[inode].prepacked_data->normalized_T_arr, tnode[inode].prepacked_data->normalized_total_critical_paths); } else { if (g_sdc->num_constrained_clocks <= 1) { fprintf(fp, "--\t\t\t--\t\t\t"); } fprintf(fp, "--\t\t--\t\t--\n"); } } fclose(fp); } #endif /* Count # of tnodes, allocates space, and loads the tnodes and its associated edges */ static void alloc_and_load_tnodes(t_timing_inf timing_inf) { int i, j, k; int inode; int num_nodes_in_block; int count; int iblock, irr_node; int inet, dport, dpin, dblock, dnode; int normalized_pin, normalization; t_pb_graph_pin *ipb_graph_pin; t_rr_node *local_rr_graph, *d_rr_graph; int num_dangling_pins; f_net_to_driver_tnode = (int*)my_malloc(num_timing_nets * sizeof(int)); for (i = 0; i < num_timing_nets; i++) { f_net_to_driver_tnode[i] = OPEN; } /* allocate space for tnodes */ num_tnodes = 0; for (i = 0; i < num_blocks; i++) { num_nodes_in_block = 0; for (j = 0; j < block[i].pb->pb_graph_node->total_pb_pins; j++) { if (block[i].pb->rr_graph[j].net_num != OPEN) { if (block[i].pb->rr_graph[j].pb_graph_pin->type == PB_PIN_INPAD || block[i].pb->rr_graph[j].pb_graph_pin->type == PB_PIN_OUTPAD || block[i].pb->rr_graph[j].pb_graph_pin->type == PB_PIN_SEQUENTIAL) { num_nodes_in_block += 2; } else { num_nodes_in_block++; } } } num_tnodes += num_nodes_in_block; } tnode = (t_tnode*)my_calloc(num_tnodes, sizeof(t_tnode)); /* load tnodes with all info except edge info */ /* populate tnode lookups for edge info */ inode = 0; for (i = 0; i < num_blocks; i++) { for (j = 0; j < block[i].pb->pb_graph_node->total_pb_pins; j++) { if (block[i].pb->rr_graph[j].net_num != OPEN) { assert(tnode[inode].pb_graph_pin == NULL); load_tnode(block[i].pb->rr_graph[j].pb_graph_pin, i, &inode, timing_inf); } } } assert(inode == num_tnodes); num_dangling_pins = 0; /* load edge delays and initialize clock domains to OPEN and prepacked_data (which is not used post-packing) to NULL. */ for (i = 0; i < num_tnodes; i++) { tnode[i].clock_domain = OPEN; tnode[i].prepacked_data = NULL; /* 3 primary scenarios for edge delays 1. Point-to-point delays inside block 2. */ count = 0; iblock = tnode[i].block; switch (tnode[i].type) { case TN_INPAD_OPIN: case TN_INTERMEDIATE_NODE: case TN_PRIMITIVE_OPIN: case TN_FF_OPIN: case TN_CB_IPIN: /* fanout is determined by intra-cluster connections */ /* Allocate space for edges */ irr_node = tnode[i].pb_graph_pin->pin_count_in_cluster; local_rr_graph = block[iblock].pb->rr_graph; ipb_graph_pin = local_rr_graph[irr_node].pb_graph_pin; if (ipb_graph_pin->parent_node->pb_type->max_internal_delay != UNDEFINED) { if (pb_max_internal_delay == UNDEFINED) { pb_max_internal_delay = ipb_graph_pin->parent_node->pb_type->max_internal_delay; pbtype_max_internal_delay = ipb_graph_pin->parent_node->pb_type; } else if (pb_max_internal_delay < ipb_graph_pin->parent_node->pb_type->max_internal_delay) { pb_max_internal_delay = ipb_graph_pin->parent_node->pb_type->max_internal_delay; pbtype_max_internal_delay = ipb_graph_pin->parent_node->pb_type; } } for (j = 0; j < block[iblock].pb->rr_graph[irr_node].num_edges; j++) { dnode = local_rr_graph[irr_node].edges[j]; if ((local_rr_graph[dnode].prev_node == irr_node) && (j == local_rr_graph[dnode].prev_edge)) { count++; } } assert(count > 0); tnode[i].num_edges = count; tnode[i].out_edges = (t_tedge *) my_chunk_malloc( count * sizeof(t_tedge), &tedge_ch); /* Load edges */ count = 0; for (j = 0; j < local_rr_graph[irr_node].num_edges; j++) { dnode = local_rr_graph[irr_node].edges[j]; if ((local_rr_graph[dnode].prev_node == irr_node) && (j == local_rr_graph[dnode].prev_edge)) { assert( (ipb_graph_pin->output_edges[j]->num_output_pins == 1) && (local_rr_graph[ipb_graph_pin->output_edges[j]->output_pins[0]->pin_count_in_cluster].net_num == local_rr_graph[irr_node].net_num)); tnode[i].out_edges[count].Tdel = ipb_graph_pin->output_edges[j]->delay_max; tnode[i].out_edges[count].to_node = get_tnode_index(local_rr_graph[dnode].tnode); if (vpack_net[local_rr_graph[irr_node].net_num].is_const_gen == TRUE && tnode[i].type == TN_PRIMITIVE_OPIN) { tnode[i].out_edges[count].Tdel = HUGE_NEGATIVE_FLOAT; tnode[i].type = TN_CONSTANT_GEN_SOURCE; } count++; } } assert(count > 0); break; case TN_PRIMITIVE_IPIN: /* Pin info comes from pb_graph block delays */ /*there would be no slack information if timing analysis is off*/ if (timing_inf.timing_analysis_enabled) { irr_node = tnode[i].pb_graph_pin->pin_count_in_cluster; local_rr_graph = block[iblock].pb->rr_graph; ipb_graph_pin = local_rr_graph[irr_node].pb_graph_pin; tnode[i].num_edges = ipb_graph_pin->num_pin_timing; tnode[i].out_edges = (t_tedge *) my_chunk_malloc( ipb_graph_pin->num_pin_timing * sizeof(t_tedge), &tedge_ch); k = 0; for (j = 0; j < tnode[i].num_edges; j++) { /* Some outpins aren't used, ignore these. Only consider output pins that are used */ if (local_rr_graph[ipb_graph_pin->pin_timing[j]->pin_count_in_cluster].net_num != OPEN) { tnode[i].out_edges[k].Tdel = ipb_graph_pin->pin_timing_del_max[j]; tnode[i].out_edges[k].to_node = get_tnode_index(local_rr_graph[ipb_graph_pin->pin_timing[j]->pin_count_in_cluster].tnode); assert(tnode[i].out_edges[k].to_node != OPEN); k++; } } tnode[i].num_edges -= (j - k); /* remove unused edges */ if (tnode[i].num_edges == 0) { /* Dangling pin */ num_dangling_pins++; } } break; case TN_CB_OPIN: /* load up net info */ irr_node = tnode[i].pb_graph_pin->pin_count_in_cluster; local_rr_graph = block[iblock].pb->rr_graph; ipb_graph_pin = local_rr_graph[irr_node].pb_graph_pin; assert(local_rr_graph[irr_node].net_num != OPEN); inet = vpack_to_clb_net_mapping[local_rr_graph[irr_node].net_num]; assert(inet != OPEN); f_net_to_driver_tnode[inet] = i; tnode[i].num_edges = clb_net[inet].num_sinks; tnode[i].out_edges = (t_tedge *) my_chunk_malloc( clb_net[inet].num_sinks * sizeof(t_tedge), &tedge_ch); for (j = 1; j <= clb_net[inet].num_sinks; j++) { dblock = clb_net[inet].node_block[j]; normalization = block[dblock].type->num_pins / block[dblock].type->capacity; normalized_pin = clb_net[inet].node_block_pin[j] % normalization; d_rr_graph = block[dblock].pb->rr_graph; dpin = OPEN; dport = OPEN; count = 0; for (k = 0; k < block[dblock].pb->pb_graph_node->num_input_ports && dpin == OPEN; k++) { if (normalized_pin >= count && (count + block[dblock].pb->pb_graph_node->num_input_pins[k] > normalized_pin)) { dpin = normalized_pin - count; dport = k; break; } count += block[dblock].pb->pb_graph_node->num_input_pins[k]; } if (dpin == OPEN) { for (k = 0; k < block[dblock].pb->pb_graph_node->num_output_ports && dpin == OPEN; k++) { count += block[dblock].pb->pb_graph_node->num_output_pins[k]; } for (k = 0; k < block[dblock].pb->pb_graph_node->num_clock_ports && dpin == OPEN; k++) { if (normalized_pin >= count && (count + block[dblock].pb->pb_graph_node->num_clock_pins[k] > normalized_pin)) { dpin = normalized_pin - count; dport = k; } count += block[dblock].pb->pb_graph_node->num_clock_pins[k]; } assert(dpin != OPEN); assert( inet == vpack_to_clb_net_mapping[d_rr_graph[block[dblock].pb->pb_graph_node->clock_pins[dport][dpin].pin_count_in_cluster].net_num]); tnode[i].out_edges[j - 1].to_node = get_tnode_index(d_rr_graph[block[dblock].pb->pb_graph_node->clock_pins[dport][dpin].pin_count_in_cluster].tnode); } else { assert(dpin != OPEN); assert(inet == vpack_to_clb_net_mapping[d_rr_graph[block[dblock].pb->pb_graph_node->input_pins[dport][dpin].pin_count_in_cluster].net_num]); /* delays are assigned post routing */ tnode[i].out_edges[j - 1].to_node = get_tnode_index(d_rr_graph[block[dblock].pb->pb_graph_node->input_pins[dport][dpin].pin_count_in_cluster].tnode); } tnode[i].out_edges[j - 1].Tdel = 0; assert(inet != OPEN); } break; case TN_OUTPAD_IPIN: case TN_INPAD_SOURCE: case TN_OUTPAD_SINK: case TN_FF_SINK: case TN_FF_SOURCE: case TN_FF_IPIN: case TN_FF_CLOCK: break; default: vpr_printf(TIO_MESSAGE_ERROR, "Consistency check failed: Unknown tnode type %d.\n", tnode[i].type); assert(0); break; } } if(num_dangling_pins > 0) { vpr_printf(TIO_MESSAGE_WARNING, "Unconnected logic in design, number of dangling tnodes = %d\n", num_dangling_pins); } } /* Allocate timing graph for pre packed netlist Count number of tnodes first Then connect up tnodes with edges */ static void alloc_and_load_tnodes_from_prepacked_netlist(float block_delay, float inter_cluster_net_delay) { int i, j, k; t_model *model; t_model_ports *model_port; t_pb_graph_pin *from_pb_graph_pin, *to_pb_graph_pin; int inode, inet; int incr; int count; f_net_to_driver_tnode = (int*)my_malloc(num_logical_nets * sizeof(int)); for (i = 0; i < num_logical_nets; i++) { f_net_to_driver_tnode[i] = OPEN; } /* allocate space for tnodes */ num_tnodes = 0; for (i = 0; i < num_logical_blocks; i++) { model = logical_block[i].model; logical_block[i].clock_net_tnode = NULL; if (logical_block[i].type == VPACK_INPAD) { logical_block[i].output_net_tnodes = (t_tnode***)my_calloc(1, sizeof(t_tnode**)); num_tnodes += 2; } else if (logical_block[i].type == VPACK_OUTPAD) { logical_block[i].input_net_tnodes = (t_tnode***)my_calloc(1, sizeof(t_tnode**)); num_tnodes += 2; } else { if (logical_block[i].clock_net == OPEN) { incr = 1; } else { incr = 2; } j = 0; model_port = model->inputs; while (model_port) { if (model_port->is_clock == FALSE) { for (k = 0; k < model_port->size; k++) { if (logical_block[i].input_nets[j][k] != OPEN) { num_tnodes += incr; } } j++; } else { num_tnodes++; } model_port = model_port->next; } logical_block[i].input_net_tnodes = (t_tnode ***)my_calloc(j, sizeof(t_tnode**)); j = 0; model_port = model->outputs; while (model_port) { for (k = 0; k < model_port->size; k++) { if (logical_block[i].output_nets[j][k] != OPEN) { num_tnodes += incr; } } j++; model_port = model_port->next; } logical_block[i].output_net_tnodes = (t_tnode ***)my_calloc(j, sizeof(t_tnode**)); } } tnode = (t_tnode *)my_calloc(num_tnodes, sizeof(t_tnode)); /* Allocate space for prepacked_data, which is only used pre-packing. */ for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].prepacked_data = (t_prepacked_tnode_data *) my_malloc(sizeof(t_prepacked_tnode_data)); } /* load tnodes, alloc edges for tnodes, load all known tnodes */ inode = 0; for (i = 0; i < num_logical_blocks; i++) { model = logical_block[i].model; if (logical_block[i].type == VPACK_INPAD) { logical_block[i].output_net_tnodes[0] = (t_tnode **)my_calloc(1, sizeof(t_tnode*)); logical_block[i].output_net_tnodes[0][0] = &tnode[inode]; f_net_to_driver_tnode[logical_block[i].output_nets[0][0]] = inode; tnode[inode].prepacked_data->model_pin = 0; tnode[inode].prepacked_data->model_port = 0; tnode[inode].prepacked_data->model_port_ptr = model->outputs; tnode[inode].block = i; tnode[inode].type = TN_INPAD_OPIN; tnode[inode].num_edges = vpack_net[logical_block[i].output_nets[0][0]].num_sinks; tnode[inode].out_edges = (t_tedge *) my_chunk_malloc( tnode[inode].num_edges * sizeof(t_tedge), &tedge_ch); tnode[inode + 1].num_edges = 1; tnode[inode + 1].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[inode + 1].out_edges->Tdel = 0; tnode[inode + 1].out_edges->to_node = inode; tnode[inode + 1].type = TN_INPAD_SOURCE; tnode[inode + 1].block = i; inode += 2; } else if (logical_block[i].type == VPACK_OUTPAD) { logical_block[i].input_net_tnodes[0] = (t_tnode **)my_calloc(1, sizeof(t_tnode*)); logical_block[i].input_net_tnodes[0][0] = &tnode[inode]; tnode[inode].prepacked_data->model_pin = 0; tnode[inode].prepacked_data->model_port = 0; tnode[inode].prepacked_data->model_port_ptr = model->inputs; tnode[inode].block = i; tnode[inode].type = TN_OUTPAD_IPIN; tnode[inode].num_edges = 1; tnode[inode].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[inode].out_edges->Tdel = 0; tnode[inode].out_edges->to_node = inode + 1; tnode[inode + 1].type = TN_OUTPAD_SINK; tnode[inode + 1].block = i; tnode[inode + 1].num_edges = 0; tnode[inode + 1].out_edges = NULL; inode += 2; } else { j = 0; model_port = model->outputs; while (model_port) { logical_block[i].output_net_tnodes[j] = (t_tnode **)my_calloc( model_port->size, sizeof(t_tnode*)); for (k = 0; k < model_port->size; k++) { if (logical_block[i].output_nets[j][k] != OPEN) { tnode[inode].prepacked_data->model_pin = k; tnode[inode].prepacked_data->model_port = j; tnode[inode].prepacked_data->model_port_ptr = model_port; tnode[inode].block = i; f_net_to_driver_tnode[logical_block[i].output_nets[j][k]] = inode; logical_block[i].output_net_tnodes[j][k] = &tnode[inode]; tnode[inode].num_edges = vpack_net[logical_block[i].output_nets[j][k]].num_sinks; tnode[inode].out_edges = (t_tedge *) my_chunk_malloc( tnode[inode].num_edges * sizeof(t_tedge), &tedge_ch); if (logical_block[i].clock_net == OPEN) { tnode[inode].type = TN_PRIMITIVE_OPIN; inode++; } else { /* load delays from predicted clock-to-Q time */ from_pb_graph_pin = get_pb_graph_node_pin_from_model_port_pin(model_port, k, logical_block[i].expected_lowest_cost_primitive); tnode[inode].type = TN_FF_OPIN; tnode[inode + 1].num_edges = 1; tnode[inode + 1].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[inode + 1].out_edges->to_node = inode; tnode[inode + 1].out_edges->Tdel = from_pb_graph_pin->tsu_tco; tnode[inode + 1].type = TN_FF_SOURCE; tnode[inode + 1].block = i; inode += 2; } } } j++; model_port = model_port->next; } j = 0; model_port = model->inputs; while (model_port) { if (model_port->is_clock == FALSE) { logical_block[i].input_net_tnodes[j] = (t_tnode **)my_calloc( model_port->size, sizeof(t_tnode*)); for (k = 0; k < model_port->size; k++) { if (logical_block[i].input_nets[j][k] != OPEN) { tnode[inode].prepacked_data->model_pin = k; tnode[inode].prepacked_data->model_port = j; tnode[inode].prepacked_data->model_port_ptr = model_port; tnode[inode].block = i; logical_block[i].input_net_tnodes[j][k] = &tnode[inode]; from_pb_graph_pin = get_pb_graph_node_pin_from_model_port_pin(model_port, k, logical_block[i].expected_lowest_cost_primitive); if (logical_block[i].clock_net == OPEN) { /* load predicted combinational delays to predicted edges */ tnode[inode].type = TN_PRIMITIVE_IPIN; tnode[inode].out_edges = (t_tedge *) my_chunk_malloc( from_pb_graph_pin->num_pin_timing * sizeof(t_tedge), &tedge_ch); count = 0; for(int m = 0; m < from_pb_graph_pin->num_pin_timing; m++) { to_pb_graph_pin = from_pb_graph_pin->pin_timing[m]; if(logical_block[i].output_nets[to_pb_graph_pin->port->model_port->index][to_pb_graph_pin->pin_number] == OPEN) { continue; } tnode[inode].out_edges[count].Tdel = from_pb_graph_pin->pin_timing_del_max[m]; tnode[inode].out_edges[count].to_node = get_tnode_index(logical_block[i].output_net_tnodes[to_pb_graph_pin->port->model_port->index][to_pb_graph_pin->pin_number]); count++; } tnode[inode].num_edges = count; inode++; } else { /* load predicted setup time */ tnode[inode].type = TN_FF_IPIN; tnode[inode].num_edges = 1; tnode[inode].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[inode].out_edges->to_node = inode + 1; tnode[inode].out_edges->Tdel = from_pb_graph_pin->tsu_tco; tnode[inode + 1].type = TN_FF_SINK; tnode[inode + 1].num_edges = 0; tnode[inode + 1].out_edges = NULL; tnode[inode + 1].block = i; inode += 2; } } } j++; } else { if (logical_block[i].clock_net != OPEN) { assert(logical_block[i].clock_net_tnode == NULL); logical_block[i].clock_net_tnode = &tnode[inode]; tnode[inode].block = i; tnode[inode].prepacked_data->model_pin = 0; tnode[inode].prepacked_data->model_port = 0; tnode[inode].prepacked_data->model_port_ptr = model_port; tnode[inode].num_edges = 0; tnode[inode].out_edges = NULL; tnode[inode].type = TN_FF_CLOCK; inode++; } } model_port = model_port->next; } } } assert(inode == num_tnodes); /* load edge delays and initialize clock domains to OPEN. */ for (i = 0; i < num_tnodes; i++) { tnode[i].clock_domain = OPEN; /* 3 primary scenarios for edge delays 1. Point-to-point delays inside block 2. */ count = 0; switch (tnode[i].type) { case TN_INPAD_OPIN: case TN_PRIMITIVE_OPIN: case TN_FF_OPIN: /* fanout is determined by intra-cluster connections */ /* Allocate space for edges */ inet = logical_block[tnode[i].block].output_nets[tnode[i].prepacked_data->model_port][tnode[i].prepacked_data->model_pin]; assert(inet != OPEN); for (j = 1; j <= vpack_net[inet].num_sinks; j++) { if (vpack_net[inet].is_const_gen) { tnode[i].out_edges[j - 1].Tdel = HUGE_NEGATIVE_FLOAT; tnode[i].type = TN_CONSTANT_GEN_SOURCE; } else { tnode[i].out_edges[j - 1].Tdel = inter_cluster_net_delay; } if (vpack_net[inet].is_global) { assert( logical_block[vpack_net[inet].node_block[j]].clock_net == inet); tnode[i].out_edges[j - 1].to_node = get_tnode_index(logical_block[vpack_net[inet].node_block[j]].clock_net_tnode); } else { assert( logical_block[vpack_net[inet].node_block[j]].input_net_tnodes[vpack_net[inet].node_block_port[j]][vpack_net[inet].node_block_pin[j]] != NULL); tnode[i].out_edges[j - 1].to_node = get_tnode_index(logical_block[vpack_net[inet].node_block[j]].input_net_tnodes[vpack_net[inet].node_block_port[j]][vpack_net[inet].node_block_pin[j]]); } } assert(tnode[i].num_edges == vpack_net[inet].num_sinks); break; case TN_PRIMITIVE_IPIN: case TN_OUTPAD_IPIN: case TN_INPAD_SOURCE: case TN_OUTPAD_SINK: case TN_FF_SINK: case TN_FF_SOURCE: case TN_FF_IPIN: case TN_FF_CLOCK: break; default: vpr_printf(TIO_MESSAGE_ERROR, "Consistency check failed: Unknown tnode type %d.\n", tnode[i].type); assert(0); break; } } for (i = 0; i < num_logical_nets; i++) { assert(f_net_to_driver_tnode[i] != OPEN); } } static void load_tnode(INP t_pb_graph_pin *pb_graph_pin, INP int iblock, INOUTP int *inode, INP t_timing_inf timing_inf) { int i; i = *inode; tnode[i].pb_graph_pin = pb_graph_pin; tnode[i].block = iblock; block[iblock].pb->rr_graph[pb_graph_pin->pin_count_in_cluster].tnode = &tnode[i]; if (tnode[i].pb_graph_pin->parent_node->pb_type->blif_model == NULL) { assert(tnode[i].pb_graph_pin->type == PB_PIN_NORMAL); if (tnode[i].pb_graph_pin->parent_node->parent_pb_graph_node == NULL) { if (tnode[i].pb_graph_pin->port->type == IN_PORT) { tnode[i].type = TN_CB_IPIN; } else { assert(tnode[i].pb_graph_pin->port->type == OUT_PORT); tnode[i].type = TN_CB_OPIN; } } else { tnode[i].type = TN_INTERMEDIATE_NODE; } } else { if (tnode[i].pb_graph_pin->type == PB_PIN_INPAD) { assert(tnode[i].pb_graph_pin->port->type == OUT_PORT); tnode[i].type = TN_INPAD_OPIN; tnode[i + 1].num_edges = 1; tnode[i + 1].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[i + 1].out_edges->Tdel = 0; tnode[i + 1].out_edges->to_node = i; tnode[i + 1].pb_graph_pin = pb_graph_pin; /* Necessary for propagate_clock_domain_and_skew(). */ tnode[i + 1].type = TN_INPAD_SOURCE; tnode[i + 1].block = iblock; (*inode)++; } else if (tnode[i].pb_graph_pin->type == PB_PIN_OUTPAD) { assert(tnode[i].pb_graph_pin->port->type == IN_PORT); tnode[i].type = TN_OUTPAD_IPIN; tnode[i].num_edges = 1; tnode[i].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[i].out_edges->Tdel = 0; tnode[i].out_edges->to_node = i + 1; tnode[i + 1].pb_graph_pin = pb_graph_pin; /* Necessary for find_tnode_net_name(). */ tnode[i + 1].type = TN_OUTPAD_SINK; tnode[i + 1].block = iblock; tnode[i + 1].num_edges = 0; tnode[i + 1].out_edges = NULL; (*inode)++; } else if (tnode[i].pb_graph_pin->type == PB_PIN_SEQUENTIAL) { if (tnode[i].pb_graph_pin->port->type == IN_PORT) { tnode[i].type = TN_FF_IPIN; tnode[i].num_edges = 1; tnode[i].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[i].out_edges->Tdel = pb_graph_pin->tsu_tco; tnode[i].out_edges->to_node = i + 1; tnode[i + 1].pb_graph_pin = pb_graph_pin; tnode[i + 1].type = TN_FF_SINK; tnode[i + 1].block = iblock; tnode[i + 1].num_edges = 0; tnode[i + 1].out_edges = NULL; } else { assert(tnode[i].pb_graph_pin->port->type == OUT_PORT); tnode[i].type = TN_FF_OPIN; tnode[i + 1].num_edges = 1; tnode[i + 1].out_edges = (t_tedge *) my_chunk_malloc( 1 * sizeof(t_tedge), &tedge_ch); tnode[i + 1].out_edges->Tdel = pb_graph_pin->tsu_tco; tnode[i + 1].out_edges->to_node = i; tnode[i + 1].pb_graph_pin = pb_graph_pin; tnode[i + 1].type = TN_FF_SOURCE; tnode[i + 1].block = iblock; } (*inode)++; } else if (tnode[i].pb_graph_pin->type == PB_PIN_CLOCK) { tnode[i].type = TN_FF_CLOCK; tnode[i].num_edges = 0; tnode[i].out_edges = NULL; } else { if (tnode[i].pb_graph_pin->port->type == IN_PORT) { assert(tnode[i].pb_graph_pin->type == PB_PIN_TERMINAL); tnode[i].type = TN_PRIMITIVE_IPIN; } else { assert(tnode[i].pb_graph_pin->port->type == OUT_PORT); assert(tnode[i].pb_graph_pin->type == PB_PIN_TERMINAL); tnode[i].type = TN_PRIMITIVE_OPIN; } } } (*inode)++; } void print_timing_graph(const char *fname) { /* Prints the timing graph into a file. */ FILE *fp; int inode, iedge, ilevel, i; t_tedge *tedge; e_tnode_type itype; const char *tnode_type_names[] = { "TN_INPAD_SOURCE", "TN_INPAD_OPIN", "TN_OUTPAD_IPIN", "TN_OUTPAD_SINK", "TN_CB_IPIN", "TN_CB_OPIN", "TN_INTERMEDIATE_NODE", "TN_PRIMITIVE_IPIN", "TN_PRIMITIVE_OPIN", "TN_FF_IPIN", "TN_FF_OPIN", "TN_FF_SINK", "TN_FF_SOURCE", "TN_FF_CLOCK", "TN_CONSTANT_GEN_SOURCE" }; fp = my_fopen(fname, "w", 0); fprintf(fp, "num_tnodes: %d\n", num_tnodes); fprintf(fp, "Node #\tType\t\tipin\tiblk\tDomain\tSkew\tI/O Delay\t# edges\t" "to_node Tdel\n\n"); for (inode = 0; inode < num_tnodes; inode++) { fprintf(fp, "%d\t", inode); itype = tnode[inode].type; fprintf(fp, "%-15.15s\t", tnode_type_names[itype]); if (tnode[inode].pb_graph_pin != NULL) { fprintf(fp, "%d\t%d\t", tnode[inode].pb_graph_pin->pin_count_in_cluster, tnode[inode].block); } else { fprintf(fp, "\t%d\t", tnode[inode].block); } if (itype == TN_FF_CLOCK || itype == TN_FF_SOURCE || itype == TN_FF_SINK) { fprintf(fp, "%d\t%.3e\t\t", tnode[inode].clock_domain, tnode[inode].clock_delay); } else if (itype == TN_INPAD_SOURCE) { fprintf(fp, "%d\t\t%.3e\t", tnode[inode].clock_domain, tnode[inode].out_edges[0].Tdel); } else if (itype == TN_OUTPAD_SINK) { assert(tnode[inode-1].type == TN_OUTPAD_IPIN); /* Outpad ipins should be one prior in the tnode array */ fprintf(fp, "%d\t\t%.3e\t", tnode[inode].clock_domain, tnode[inode-1].out_edges[0].Tdel); } else { fprintf(fp, "\t\t\t\t"); } fprintf(fp, "%d", tnode[inode].num_edges); /* Print all edges after edge 0 on separate lines */ tedge = tnode[inode].out_edges; if (tnode[inode].num_edges > 0) { fprintf(fp, "\t%4d\t%7.3g", tedge[0].to_node, tedge[0].Tdel); for (iedge = 1; iedge < tnode[inode].num_edges; iedge++) { fprintf(fp, "\n\t\t\t\t\t\t\t\t\t\t%4d\t%7.3g", tedge[iedge].to_node, tedge[iedge].Tdel); } } fprintf(fp, "\n"); } fprintf(fp, "\n\nnum_tnode_levels: %d\n", num_tnode_levels); for (ilevel = 0; ilevel < num_tnode_levels; ilevel++) { fprintf(fp, "\n\nLevel: %d Num_nodes: %d\nNodes:", ilevel, tnodes_at_level[ilevel].nelem); for (i = 0; i < tnodes_at_level[ilevel].nelem; i++) fprintf(fp, "\t%d", tnodes_at_level[ilevel].list[i]); } fprintf(fp, "\n"); fprintf(fp, "\n\nNet #\tNet_to_driver_tnode\n"); for (i = 0; i < num_nets; i++) fprintf(fp, "%4d\t%6d\n", i, f_net_to_driver_tnode[i]); if (g_sdc->num_constrained_clocks == 1) { /* Arrival and required times, and forward and backward weights, will be meaningless for multiclock designs, since the values currently on the graph will only correspond to the most recent traversal. */ fprintf(fp, "\n\nNode #\t\tT_arr\t\tT_req" #ifdef PATH_COUNTING "\tForward weight\tBackward weight" #endif "\n\n"); for (inode = 0; inode < num_tnodes; inode++) { if (tnode[inode].T_arr > HUGE_NEGATIVE_FLOAT + 1) { fprintf(fp, "%d\t%12g", inode, tnode[inode].T_arr); } else { fprintf(fp, "%d\t\t -", inode); } if (tnode[inode].T_req < HUGE_POSITIVE_FLOAT - 1) { fprintf(fp, "\t%12g", tnode[inode].T_req); } else { fprintf(fp, "\t\t -"); } #ifdef PATH_COUNTING fprintf(fp, "\t%12g\t%12g\n", tnode[inode].forward_weight, tnode[inode].backward_weight); #endif } } fclose(fp); } static void process_constraints(void) { /* Removes all constraints between domains which never intersect. We need to do this so that criticality_denom in do_timing_analysis is not affected by unused constraints. BFS through the levelized graph once for each source domain. Whenever we get to a sink, mark off that we've used that sink clock domain. After each traversal, set all unused constraints to DO_NOT_ANALYSE. Also, print g_sdc->domain_constraints, constrained I/Os and override constraints, and convert g_sdc->domain_constraints and flip-flop-level override constraints to be in seconds rather than nanoseconds. We don't need to normalize g_sdc->cc_constraints because they're already on the g_sdc->domain_constraints matrix, and we don't need to normalize constrained_ios because we already did the normalization when we put the delays onto the timing graph in load_clock_domain_and_clock_and_io_delay. */ int source_clock_domain, sink_clock_domain, inode, ilevel, num_at_level, i, num_edges, iedge, to_node, icf, ifc, iff; t_tedge * tedge; float constraint; boolean * constraint_used = (boolean *) my_malloc(g_sdc->num_constrained_clocks * sizeof(boolean)); for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { /* We're going to use arrival time to flag which nodes we've reached, even though the values we put in will not correspond to actual arrival times. Nodes which are reached on this traversal will get an arrival time of 0. Reset arrival times now to an invalid number. */ for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].T_arr = HUGE_NEGATIVE_FLOAT; } /* Reset all constraint_used entries. */ for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { constraint_used[sink_clock_domain] = FALSE; } /* Set arrival times for each top-level tnode on this clock domain. */ num_at_level = tnodes_at_level[0].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[0].list[i]; if (tnode[inode].clock_domain == source_clock_domain) { tnode[inode].T_arr = 0.; } } for (ilevel = 0; ilevel < num_tnode_levels; ilevel++) { /* Go down one level at a time. */ num_at_level = tnodes_at_level[ilevel].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; /* Go through each of the tnodes at the level we're on. */ if (has_valid_T_arr(inode)) { /* If this tnode has been used */ num_edges = tnode[inode].num_edges; if (num_edges == 0) { /* sink */ /* We've reached the sink domain of this tnode, so set constraint_used to true for this tnode's clock domain (if it has a valid one). */ sink_clock_domain = tnode[inode].clock_domain; if (sink_clock_domain != -1) { constraint_used[sink_clock_domain] = TRUE; } } else { /* Set arrival time to a valid value (0.) for each tnode in this tnode's fanout. */ tedge = tnode[inode].out_edges; for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; tnode[to_node].T_arr = 0.; } } } } } /* At the end of the source domain traversal, see which sink domains haven't been hit, and set the constraint for the pair of source and sink domains to DO_NOT_ANALYSE */ for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (!constraint_used[sink_clock_domain]) { g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = DO_NOT_ANALYSE; } } } free(constraint_used); /* Print constraints */ if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_TIMING_CONSTRAINTS)) { print_timing_constraint_info(getEchoFileName(E_ECHO_TIMING_CONSTRAINTS)); } /* Convert g_sdc->domain_constraint and ff-level override constraints to be in seconds, not nanoseconds. */ for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { constraint = g_sdc->domain_constraint[source_clock_domain][sink_clock_domain]; if (constraint > NEGATIVE_EPSILON) { /* if constraint does not equal DO_NOT_ANALYSE */ g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = constraint * 1e-9; } } } for (icf = 0; icf < g_sdc->num_cf_constraints; icf++) { g_sdc->cf_constraints[icf].constraint *= 1e-9; } for (ifc = 0; ifc < g_sdc->num_fc_constraints; ifc++) { g_sdc->fc_constraints[ifc].constraint *= 1e-9; } for (iff = 0; iff < g_sdc->num_ff_constraints; iff++) { g_sdc->ff_constraints[iff].constraint *= 1e-9; } /* Finally, free g_sdc->cc_constraints since all of its info is contained in g_sdc->domain_constraint. */ free_override_constraint(g_sdc->cc_constraints, g_sdc->num_cc_constraints); } static void alloc_timing_stats(void) { /* Allocate f_timing_stats data structure. */ int i; f_timing_stats = (t_timing_stats *) my_malloc(sizeof(t_timing_stats)); f_timing_stats->cpd = (float **) my_malloc(g_sdc->num_constrained_clocks * sizeof(float *)); f_timing_stats->least_slack = (float **) my_malloc(g_sdc->num_constrained_clocks * sizeof(float *)); for (i = 0; i < g_sdc->num_constrained_clocks; i++) { f_timing_stats->cpd[i] = (float *) my_malloc(g_sdc->num_constrained_clocks * sizeof(float)); f_timing_stats->least_slack[i] = (float *) my_malloc(g_sdc->num_constrained_clocks * sizeof(float)); } } void do_timing_analysis(t_slack * slacks, boolean is_prepacked, boolean do_lut_input_balancing, boolean is_final_analysis) { /* Performs timing analysis on the circuit. Before this routine is called, t_slack * slacks must have been allocated, and the circuit must have been converted into a timing graph. The nodes of the timing graph represent pins and the edges between them represent delays and m dependencies from one pin to another. Most elements are modeled as a pair of nodes so that the delay through the element can be marked on the edge between them (e.g. TN_INPAD_SOURCE->TN_INPAD_OPIN, TN_OUTPAD_IPIN->TN_OUTPAD_SINK, TN_PRIMITIVE_OPIN-> TN_PRIMITIVE_OPIN, etc.). The timing graph nodes are stored as an array, tnode [0..num_tnodes - 1]. Each tnode includes an array of all edges, tedge, which fan out from it. Each tedge includes the index of the node on its far end (in the tnode array), and the delay to that node. The timing graph has sources at each TN_FF_SOURCE (Q output), TN_INPAD_SOURCE (input I/O pad) and TN_CONSTANT_GEN_SOURCE (constant 1 or 0 generator) node and sinks at TN_FF_SINK (D input) and TN_OUTPAD_SINK (output I/O pad) nodes. Two traversals, one forward (sources to sinks) and one backward, are performed for each valid constraint (one which is not DO_NOT_ANALYSE) between a source and a sink clock domain in the matrix g_sdc->domain_constraint [0..g_sdc->num_constrained_clocks - 1][0..g_sdc->num_constrained_clocks - 1]. This matrix has been pruned so that all domain pairs with no paths between them have been set to DO_NOT_ANALYSE. During the traversal pair for each constraint, all nodes in the fanout of sources on the source clock domain are assigned a T_arr, the arrival time of the last input signal to the node. All nodes in the fanin of sinks on the sink clock domain are assigned a T_req, the required arrival time of the last input signal to the node if the critical path for this constraint is not to be lengthened. Nodes which receive both a valid T_arr and T_req are flagged with used_on_this_traversal, and nodes which are used on at least one traversal pair are flagged with has_valid_slack so that later functions know the slack is valid. After each traversal pair, a slack is calculated for each sink pin on each net (or equivalently, each connection or tedge fanning out from that net's driver tnode). Slack is calculated as: T_req (dest node) - T_arr (source node) - Tdel (edge) and represents the amount of delay which could be added to this connection before the critical path delay for this constraint would worsen. Edges on the critical path have a slack of 0. Slacks which are never used are set to HUGE_POSITIVE_FLOAT. The optimizers actually use a metric called timing_criticality. Timing criticality is defined as 1 - slack / criticality_denom, where the normalization factor criticality_denom is the max of all arrival times in the constraint and the constraint itself (T_req-relaxed slacks) or all arrival times and constraints in the design (shifted slacks). See below for a further discussion of these two regimes. Timing criticality is always between 0 (not critical) and 1 (very critical). Unlike slack, which is set to HUGE_POSITIVE_FLOAT for unanalysed connections, timing criticality is 0 for these, meaning no special check has to be made for which connections have been analysed. If path counting is on (PATH_COUNTING is defined in vpr_types.h), the optimizers use a weighted sum of timing_criticality and path_criticality, the latter of which is an estimate of the importance of the number of paths using a particular connection. As a path's timing_criticality decreases, it will become exponentially less important to the path_criticality of any connection which this path uses. Path criticality also goes from 0 (not critical or unanalysed) to 1. Slack and criticalities are only calculated if both the driver of the net and the sink pin were used_on_this_traversal, and are only overwritten if lower than previously-obtained values. The optimizers actually use criticality rather than slack, but slack is useful for human designers and so we calculate it only if we need to print it. This routine outputs slack and criticality to t_slack * slacks. It also stores least slack and critical path delay per constraint [0..g_sdc->num_constrained_clocks - 1][0..g_sdc->num_constrained_clocks - 1] in the file-scope variable f_timing_stats. Is_prepacked flags whether to calculate normalized costs for the clusterer (normalized_slack, normalized_Tarr, normalized_total_critical_paths). Setting this to FALSE saves time in post- packed timing analyses. Do_lut_input_balancing flags whether to rebalance LUT inputs. LUT rebalancing takes advantage of the fact that different LUT inputs often have different delays. Since we can freely permute which LUT inputs are used by just changing the logic in the LUT, these LUT permutations can be performed late into the routing stage of the flow. Is_final_analysis flags whether this is the final, analysis pass. If it is, the analyser will compute actual slacks instead of relaxed ones. We "relax" slacks by setting the required time to the maximum arrival time for tight constraints so that no slacks are negative (which confuses the optimizers). This is called "T_req-relaxed" slack. However, human designers want to see actual slack values, so we report those in the final analysis. The alternative way of making slacks positive is shifting them upwards by the value of the largest negative slack, after all traversals are complete ("shifted slacks"), which can be enabled by changing SLACK_DEFINITION from 'R' to 'S' in path_delay.h. To do: flip-flop to flip-flop and flip-flop to clock domain constraints (set_false_path, set_max_delay, and especially set_multicycle_path). All the info for these constraints is contained in g_sdc->fc_constraints and g_sdc->ff_constraints, but graph traversals are not included yet. Probably, an entire traversal will be needed for each constraint. Clock domain to flip-flop constraints are coded but not tested, and are done within existing traversals. */ int i, j, source_clock_domain, sink_clock_domain, inode, inet, ipin; #if defined PATH_COUNTING || SLACK_DEFINITION == 'S' int iedge, num_edges; #endif #ifdef PATH_COUNTING float max_path_criticality = HUGE_NEGATIVE_FLOAT /* used to normalize path_criticalities */; #endif boolean update_slack = (boolean) (is_final_analysis || getEchoEnabled()); /* Only update slack values if we need to print it, i.e. for the final output file (is_final_analysis) or echo files. */ float criticality_denom; /* (SLACK_DEFINITION == 'R' only) For a particular constraint, the maximum of the constraint and all arrival times for the constraint's traversal. Used to normalize the clusterer's normalized_slack and, more importantly, criticality. */ long max_critical_output_paths, max_critical_input_paths; t_pb *pb; #if SLACK_DEFINITION == 'S' float smallest_slack_in_design = HUGE_POSITIVE_FLOAT; /* Shift all slacks upwards by this number if it is negative. */ float criticality_denom_global = HUGE_NEGATIVE_FLOAT; /* Denominator of criticality for shifted - max of all arrival times and all constraints. */ #endif /* Reset LUT input rebalancing. */ for (inode = 0; inode < num_tnodes; inode++) { if (tnode[inode].type == TN_PRIMITIVE_OPIN && tnode[inode].pb_graph_pin != NULL) { pb = block[tnode[inode].block].pb->rr_node_to_pb_mapping[tnode[inode].pb_graph_pin->pin_count_in_cluster]; if (pb != NULL && pb->lut_pin_remap != NULL) { /* this is a LUT primitive, do pin swapping */ assert(pb->pb_graph_node->pb_type->num_output_pins == 1 && pb->pb_graph_node->pb_type->num_clock_pins == 0); /* ensure LUT properties are valid */ assert(pb->pb_graph_node->num_input_ports == 1); /* If all input pins are known, perform LUT input delay rebalancing, do nothing otherwise */ for (i = 0; i < pb->pb_graph_node->num_input_pins[0]; i++) { pb->lut_pin_remap[i] = OPEN; } } } } /* Reset all values which need to be reset once per timing analysis, rather than once per traversal pair. */ /* Reset slack and criticality */ for (inet = 0; inet < num_timing_nets; inet++) { for (ipin = 1; ipin <= timing_nets[inet].num_sinks; ipin++) { slacks->slack[inet][ipin] = HUGE_POSITIVE_FLOAT; slacks->timing_criticality[inet][ipin] = 0.; #ifdef PATH_COUNTING slacks->path_criticality[inet][ipin] = 0.; #endif } } /* Reset f_timing_stats. */ for (i = 0; i < g_sdc->num_constrained_clocks; i++) { for (j = 0; j < g_sdc->num_constrained_clocks; j++) { f_timing_stats->cpd[i][j] = HUGE_NEGATIVE_FLOAT; f_timing_stats->least_slack[i][j] = HUGE_POSITIVE_FLOAT; } } #ifndef PATH_COUNTING /* Reset normalized values for clusterer. */ if (is_prepacked) { for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].prepacked_data->normalized_slack = HUGE_POSITIVE_FLOAT; tnode[inode].prepacked_data->normalized_T_arr = HUGE_NEGATIVE_FLOAT; tnode[inode].prepacked_data->normalized_total_critical_paths = HUGE_NEGATIVE_FLOAT; } } #endif if (do_lut_input_balancing) { do_lut_rebalancing(); } /* For each valid constraint (pair of source and sink clock domains), we do one forward and one backward topological traversal to find arrival and required times, in do_timing_analysis_for_constraint. If path counting is on, we then do another, simpler traversal to find forward and backward weights, relying on the largest required time we found from the first traversal. After each constraint's traversals, we update the slacks, timing criticalities and (if necessary) path criticalities or normalized costs used by the clusterer. */ for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] > NEGATIVE_EPSILON) { /* i.e. != DO_NOT_ANALYSE */ /* Perform the forward and backward traversal for this constraint. */ criticality_denom = do_timing_analysis_for_constraint(source_clock_domain, sink_clock_domain, is_prepacked, is_final_analysis, &max_critical_input_paths, &max_critical_output_paths); #ifdef PATH_COUNTING /* Weight the importance of each net, used in slack calculation. */ do_path_counting(criticality_denom); #endif /* Update the slack and criticality for each edge of each net which was analysed on the most recent traversal and has a lower (slack) or higher (criticality) value than before. */ update_slacks(slacks, source_clock_domain, sink_clock_domain, criticality_denom, update_slack); #ifndef PATH_COUNTING /* Update the normalized costs used by the clusterer. */ if (is_prepacked) { update_normalized_costs(criticality_denom, max_critical_input_paths, max_critical_output_paths); } #endif #if SLACK_DEFINITION == 'S' /* Set criticality_denom_global to the max of criticality_denom over all traversals. */ criticality_denom_global = std::max(criticality_denom_global, criticality_denom); #endif } } } #ifdef PATH_COUNTING /* Normalize path criticalities by the largest value in the circuit. Otherwise, path criticalities would be unbounded. */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { max_path_criticality = std::max(max_path_criticality, slacks->path_criticality[inet][iedge + 1]); } } for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { slacks->path_criticality[inet][iedge + 1] /= max_path_criticality; } } #endif #if SLACK_DEFINITION == 'S' if (!is_final_analysis) { /* Find the smallest slack in the design. */ for (i = 0; i < g_sdc->num_constrained_clocks; i++) { for (j = 0; j < g_sdc->num_constrained_clocks; j++) { smallest_slack_in_design = std::min(smallest_slack_in_design, f_timing_stats->least_slack[i][j]); } } /* Increase all slacks by the value of the smallest slack in the design, if it's negative. */ if (smallest_slack_in_design < 0) { for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { slacks->slack[inet][iedge + 1] -= smallest_slack_in_design; /* Remember, smallest_slack_in_design is negative, so we're INCREASING all the slacks. */ /* Note that if slack was equal to HUGE_POSITIVE_FLOAT, it will still be equal to more than this, so it will still be ignored when we calculate criticalities. */ } } } } /* We can now calculate criticalities, only after we normalize slacks. */ for (inet = 0; inet < num_timing_nets; inet++) { num_edges = timing_nets[inet].num_sinks; for (iedge = 0; iedge < num_edges; iedge++) { if (slacks->slack[inet][iedge + 1] < HUGE_POSITIVE_FLOAT - 1) { /* if the slack is valid */ slacks->timing_criticality[inet][iedge + 1] = 1 - slacks->slack[inet][iedge + 1]/criticality_denom_global; } /* otherwise, criticality remains 0, as it was initialized */ } } #endif } static void do_lut_rebalancing() { int inode, num_at_level, i, ilevel, num_edges, iedge, to_node; t_tedge * tedge; /* Reset all arrival times to a very large negative number. */ for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].T_arr = HUGE_NEGATIVE_FLOAT; } /* Set arrival times for each top-level tnode. */ num_at_level = tnodes_at_level[0].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[0].list[i]; if (tnode[inode].type == TN_FF_SOURCE) { /* Set the arrival time of this flip-flop tnode to its clock skew. */ tnode[inode].T_arr = tnode[inode].clock_delay; } else if (tnode[inode].type == TN_INPAD_SOURCE) { /* There's no such thing as clock skew for external clocks. The closest equivalent, input delay, is already marked on the edge coming out from this node. As a result, the signal can be said to arrive at t = 0. */ tnode[inode].T_arr = 0.; } } /* Now we actually start the forward topological traversal, to compute arrival times. */ for (ilevel = 0; ilevel < num_tnode_levels; ilevel++) { /* For each level of our levelized timing graph... */ num_at_level = tnodes_at_level[ilevel].nelem; /* ...there are num_at_level tnodes at that level. */ for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; /* Go through each of the tnodes at the level we're on. */ num_edges = tnode[inode].num_edges; /* Get the number of edges fanning out from the node we're visiting */ tedge = tnode[inode].out_edges; /* Get the list of edges from the node we're visiting */ for (iedge = 0; iedge < num_edges; iedge++) { /* Now go through each edge coming out from this tnode */ to_node = tedge[iedge].to_node; /* Get the index of the destination tnode of this edge. */ /* The arrival time T_arr at the destination node is set to the maximum of all the possible arrival times from all edges fanning in to the node. The arrival time represents the latest time that all inputs must arrive at a node. LUT input rebalancing also occurs at this step. */ set_and_balance_arrival_time(to_node, inode, tedge[iedge].Tdel, TRUE); } } } } static float do_timing_analysis_for_constraint(int source_clock_domain, int sink_clock_domain, boolean is_prepacked, boolean is_final_analysis, long * max_critical_input_paths_ptr, long * max_critical_output_paths_ptr) { /* Performs a single forward and backward traversal for the domain pair source_clock_domain and sink_clock_domain. Returns the denominator that will be later used to normalize criticality - the maximum of all arrival times from this traversal and the constraint for this pair of domains. Also returns the maximum number of critical input and output paths of any node analysed for this constraint, passed by reference from do_timing_analysis. */ int inode, num_at_level, i, total, ilevel, num_edges, iedge, to_node, icf; float constraint, Tdel, T_req, max_Tarr = HUGE_NEGATIVE_FLOAT; /* Max of all arrival times for this constraint - used to relax required times. */ t_tedge * tedge; int num_dangling_nodes; boolean found; long max_critical_input_paths = 0, max_critical_output_paths = 0; /* Reset all values which need to be reset once per traversal pair, rather than once per timing analysis. */ /* Reset all arrival and required times. */ for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].T_arr = HUGE_NEGATIVE_FLOAT; tnode[inode].T_req = HUGE_POSITIVE_FLOAT; } #ifndef PATH_COUNTING /* Reset num_critical_output_paths. */ if (is_prepacked) { for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].prepacked_data->num_critical_output_paths = 0; } } #endif /* Set arrival times for each top-level tnode on this source domain. */ num_at_level = tnodes_at_level[0].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[0].list[i]; if (tnode[inode].clock_domain == source_clock_domain) { if (tnode[inode].type == TN_FF_SOURCE) { /* Set the arrival time of this flip-flop tnode to its clock skew. */ tnode[inode].T_arr = tnode[inode].clock_delay; } else if (tnode[inode].type == TN_INPAD_SOURCE) { /* There's no such thing as clock skew for external clocks, and input delay is already marked on the edge coming out from this node. As a result, the signal can be said to arrive at t = 0. */ tnode[inode].T_arr = 0.; } } } /* Compute arrival times with a forward topological traversal from sources (TN_FF_SOURCE, TN_INPAD_SOURCE, TN_CONSTANT_GEN_SOURCE) to sinks (TN_FF_SINK, TN_OUTPAD_SINK). */ total = 0; /* We count up all tnodes to error-check at the end. */ for (ilevel = 0; ilevel < num_tnode_levels; ilevel++) { /* For each level of our levelized timing graph... */ num_at_level = tnodes_at_level[ilevel].nelem; /* ...there are num_at_level tnodes at that level. */ total += num_at_level; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; /* Go through each of the tnodes at the level we're on. */ if (tnode[inode].T_arr < NEGATIVE_EPSILON) { /* If the arrival time is less than 0 (i.e. HUGE_NEGATIVE_FLOAT)... */ continue; /* End this iteration of the num_at_level for loop since this node is not part of the clock domain we're analyzing. (If it were, it would have received an arrival time already.) */ } num_edges = tnode[inode].num_edges; /* Get the number of edges fanning out from the node we're visiting */ tedge = tnode[inode].out_edges; /* Get the list of edges from the node we're visiting */ #ifndef PATH_COUNTING if (is_prepacked && ilevel == 0) { tnode[inode].prepacked_data->num_critical_input_paths = 1; /* Top-level tnodes have one locally-critical input path. */ } /* Using a somewhat convoluted procedure inherited from T-VPack, count how many locally-critical input paths fan into each tnode, and also find the maximum number over all tnodes. */ if (is_prepacked) { for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; if (fabs(tnode[to_node].T_arr - (tnode[inode].T_arr + tedge[iedge].Tdel)) < EPSILON) { /* If the "local forward slack" (T_arr(to_node) - T_arr(inode) - T_del) for this edge is 0 (i.e. the path from inode to to_node is locally as critical as any other path to to_node), add to_node's num critical input paths to inode's number. */ tnode[to_node].prepacked_data->num_critical_input_paths += tnode[inode].prepacked_data->num_critical_input_paths; } else if (tnode[to_node].T_arr < (tnode[inode].T_arr + tedge[iedge].Tdel)) { /* If the "local forward slack" for this edge is negative, reset to_node's num critical input paths to inode's number. */ tnode[to_node].prepacked_data->num_critical_input_paths = tnode[inode].prepacked_data->num_critical_input_paths; } /* Set max_critical_input_paths to the maximum number of critical input paths for all tnodes analysed on this traversal. */ if (tnode[to_node].prepacked_data->num_critical_input_paths > max_critical_input_paths) { max_critical_input_paths = tnode[to_node].prepacked_data->num_critical_input_paths; } } } #endif for (iedge = 0; iedge < num_edges; iedge++) { /* Now go through each edge coming out from this tnode */ to_node = tedge[iedge].to_node; /* Get the index of the destination tnode of this edge. */ /* The arrival time T_arr at the destination node is set to the maximum of all the possible arrival times from all edges fanning in to the node. The arrival time represents the latest time that all inputs must arrive at a node. LUT input rebalancing also occurs at this step. */ set_and_balance_arrival_time(to_node, inode, tedge[iedge].Tdel, FALSE); /* Since we updated the destination node (to_node), change the max arrival time for the forward traversal if to_node's arrival time is greater than the existing maximum. */ max_Tarr = std::max(max_Tarr, tnode[to_node].T_arr); } } } assert(total == num_tnodes); num_dangling_nodes = 0; /* Compute required times with a backward topological traversal from sinks to sources. */ for (ilevel = num_tnode_levels - 1; ilevel >= 0; ilevel--) { num_at_level = tnodes_at_level[ilevel].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; num_edges = tnode[inode].num_edges; if (ilevel == 0) { if (!(tnode[inode].type == TN_INPAD_SOURCE || tnode[inode].type == TN_FF_SOURCE || tnode[inode].type == TN_CONSTANT_GEN_SOURCE)) { vpr_printf(TIO_MESSAGE_ERROR, "Timing graph started on unexpected node %s.%s[%d].\n", tnode[inode].pb_graph_pin->parent_node->pb_type->name, tnode[inode].pb_graph_pin->port->name, tnode[inode].pb_graph_pin->pin_number); vpr_printf(TIO_MESSAGE_ERROR, "This is a VPR internal error, contact VPR development team.\n"); exit(1); } } else { if ((tnode[inode].type == TN_INPAD_SOURCE || tnode[inode].type == TN_FF_SOURCE || tnode[inode].type == TN_CONSTANT_GEN_SOURCE)) { vpr_printf(TIO_MESSAGE_ERROR, "Timing graph discovered unexpected edge to node %s.%s[%d].\n", tnode[inode].pb_graph_pin->parent_node->pb_type->name, tnode[inode].pb_graph_pin->port->name, tnode[inode].pb_graph_pin->pin_number); vpr_printf(TIO_MESSAGE_ERROR, "This is a VPR internal error, contact VPR development team.\n"); exit(1); } } /* Unlike the forward traversal, the sinks are all on different levels, so we always have to check whether a node is a sink. We give every sink on the sink clock domain we're considering a valid required time. Every non-sink node in the fanin of one of these sinks and the fanout of some source from the forward traversal also gets a valid required time. */ if (num_edges == 0) { /* sink */ if (tnode[inode].type == TN_FF_CLOCK || tnode[inode].T_arr < HUGE_NEGATIVE_FLOAT + 1) { continue; /* Skip nodes on the clock net itself, and nodes with unset arrival times. */ } if (!(tnode[inode].type == TN_OUTPAD_SINK || tnode[inode].type == TN_FF_SINK)) { if(is_prepacked) { vpr_printf(TIO_MESSAGE_WARNING, "Pin on block %s.%s[%d] not used\n", logical_block[tnode[inode].block].name, tnode[inode].prepacked_data->model_port_ptr->name, tnode[inode].prepacked_data->model_pin); } num_dangling_nodes++; /* Note: Still need to do standard traversals with dangling pins so that algorithm runs properly, but T_arr and T_Req to values such that it dangling nodes do not affect actual timing values */ } /* Skip nodes not on the sink clock domain of the constraint we're currently considering */ if (tnode[inode].clock_domain != sink_clock_domain) { continue; } /* See if there's an override constraint between the source clock domain (name is g_sdc->constrained_clocks[source_clock_domain].name) and the flip-flop or outpad we're at now (name is find_tnode_net_name(inode, is_prepacked)). We test if g_sdc->num_cf_constraints > 0 first so that we can save time looking up names in the vast majority of cases where there are no such constraints. */ if (g_sdc->num_cf_constraints > 0 && (icf = find_cf_constraint(g_sdc->constrained_clocks[source_clock_domain].name, find_tnode_net_name(inode, is_prepacked))) != -1) { constraint = g_sdc->cf_constraints[icf].constraint; if (constraint < NEGATIVE_EPSILON) { /* Constraint is DO_NOT_ANALYSE (-1) for this particular sink. */ continue; } } else { /* Use the default constraint from g_sdc->domain_constraint. */ constraint = g_sdc->domain_constraint[source_clock_domain][sink_clock_domain]; /* Constraint is guaranteed to be valid since we checked for it at the very beginning. */ } /* Now we know we should analyse this tnode. */ #if SLACK_DEFINITION == 'R' /* Assign the required time T_req for this leaf node, taking into account clock skew. T_req is the time all inputs to a tnode must arrive by before it would degrade this constraint's critical path delay. Relax the required time at the sink node to be non-negative by taking the max of the "real" required time (constraint + tnode[inode].clock_delay) and the max arrival time in this domain (max_Tarr), except for the final analysis where we report actual slack. We do this to prevent any slacks from being negative, since negative slacks are not used effectively by the optimizers. E.g. if we have a 10 ns constraint and it takes 14 ns to get here, we'll have a slack of at most -4 ns for any edge along the path that got us here. If we say the required time is 14 ns (no less than the arrival time), we don't have a negative slack anymore. However, in the final timing analysis, the real slacks are computed (that's what human designers care about), not the relaxed ones. */ if (is_final_analysis) { tnode[inode].T_req = constraint + tnode[inode].clock_delay; } else { tnode[inode].T_req = std::max(constraint + tnode[inode].clock_delay, max_Tarr); } #else /* Don't do the relaxation and always set T_req equal to the "real" required time. */ tnode[inode].T_req = constraint + tnode[inode].clock_delay; #endif /* Store the largest critical path delay for this constraint (source domain AND sink domain) in the matrix critical_path_delay. C.P.D. = T_arr at destination - clock skew at destination = (datapath delay + clock delay to source) - clock delay to destination. Critical path delay is really telling us how fast we can run the source clock before we can no longer meet this constraint. e.g. If the datapath delay is 10 ns, the clock delay at source is 2 ns and the clock delay at destination is 5 ns, then C.P.D. is 7 ns by the above formula. We can run the source clock at 7 ns because the clock skew gives us 3 ns extra to meet the 10 ns datapath delay. */ f_timing_stats->cpd[source_clock_domain][sink_clock_domain] = std::max(f_timing_stats->cpd[source_clock_domain][sink_clock_domain], (tnode[inode].T_arr - tnode[inode].clock_delay)); #ifndef PATH_COUNTING if (is_prepacked) { tnode[inode].prepacked_data->num_critical_output_paths = 1; /* Bottom-level tnodes have one locally-critical input path. */ } #endif } else { /* not a sink */ assert(!(tnode[inode].type == TN_OUTPAD_SINK || tnode[inode].type == TN_FF_SINK || tnode[inode].type == TN_FF_CLOCK)); /* We need to skip this node unless it is on a path from source_clock_domain to sink_clock_domain. We need to skip all nodes which: 1. Fan out to the sink domain but do not fan in from the source domain. 2. Fan in from the source domain but do not fan out to the sink domain. 3. Do not fan in or out to either domain. If a node does not fan in from the source domain, it will not have a valid arrival time. So cases 1 and 3 can be skipped by continuing if T_arr = HUGE_NEGATIVE_FLOAT. We cannot treat case 2 as simply since the required time for this node has not yet been assigned, so we have to look at the required time for every node in its immediate fanout instead. */ /* Cases 1 and 3 */ if (tnode[inode].T_arr < HUGE_NEGATIVE_FLOAT + 1) { continue; /* Skip nodes with unset arrival times. */ } /* Case 2 */ found = FALSE; tedge = tnode[inode].out_edges; for (iedge = 0; iedge < num_edges && !found; iedge++) { to_node = tedge[iedge].to_node; if (tnode[to_node].T_req < HUGE_POSITIVE_FLOAT) { found = TRUE; } } if (!found) { continue; } /* Now we know this node is on a path from source_clock_domain to sink_clock_domain, and needs to be analyzed. */ /* Opposite to T_arr, set T_req to the MINIMUM of the required times of all edges fanning OUT from this node. */ for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; Tdel = tedge[iedge].Tdel; T_req = tnode[to_node].T_req; tnode[inode].T_req = std::min(tnode[inode].T_req, T_req - Tdel); /* Update least slack per constraint. This is NOT the same as the minimum slack we will calculate on this traversal for post-packed netlists, which only count inter-cluster slacks. We only look at edges adjacent to sink nodes on the sink clock domain since all paths go through one of these edges. */ if (tnode[to_node].num_edges == 0 && tnode[to_node].clock_domain == sink_clock_domain) { f_timing_stats->least_slack[source_clock_domain][sink_clock_domain] = std::min(f_timing_stats->least_slack[source_clock_domain][sink_clock_domain], (T_req - Tdel - tnode[inode].T_arr)); } } #ifndef PATH_COUNTING /* Similar to before, we count how many locally-critical output paths fan out from each tnode, and also find the maximum number over all tnodes. Unlike for input paths, where we haven't set the arrival time at to_node before analysing it, here the required time is set at both nodes, so the "local backward slack" (T_req(to_node) - T_req(inode) - T_del) will never be negative. Hence, we only have to test if the "local backward slack" is 0. */ if (is_prepacked) { for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; /* If the "local backward slack" (T_arr(to_node) - T_arr(inode) - T_del) for this edge is 0 (i.e. the path from inode to to_node is locally as critical as any other path to to_node), add to_node's num critical output paths to inode's number. */ if (fabs(tnode[to_node].T_req - (tnode[inode].T_req + tedge[iedge].Tdel)) < EPSILON) { tnode[inode].prepacked_data->num_critical_output_paths += tnode[to_node].prepacked_data->num_critical_output_paths; } /* Set max_critical_output_paths to the maximum number of critical output paths for all tnodes analysed on this traversal. */ if (tnode[to_node].prepacked_data->num_critical_output_paths > max_critical_output_paths) { max_critical_output_paths = tnode[to_node].prepacked_data->num_critical_output_paths; } } } #endif } } } /* Return max critical input/output paths for this constraint through the pointers we passed in. */ if (max_critical_input_paths_ptr && max_critical_output_paths_ptr) { *max_critical_input_paths_ptr = max_critical_input_paths; *max_critical_output_paths_ptr = max_critical_output_paths; } if(num_dangling_nodes > 0 && (is_final_analysis || is_prepacked)) { vpr_printf(TIO_MESSAGE_WARNING, "%d unused pins \n", num_dangling_nodes); } /* The criticality denominator is the maximum of the max arrival time and the constraint for this domain pair. */ return std::max(max_Tarr, g_sdc->domain_constraint[source_clock_domain][sink_clock_domain]); } #ifdef PATH_COUNTING static void do_path_counting(float criticality_denom) { /* Count the importance of the number of paths going through each net by giving each net a forward and backward path weight. This is the first step of "A Novel Net Weighting Algorithm for Timing-Driven Placement" (Kong, 2002). We only visit nodes with set arrival and required times, so this function must be called after do_timing_analysis_for_constraints, which sets T_arr and T_req. */ int inode, num_at_level, i, ilevel, num_edges, iedge, to_node; t_tedge * tedge; float forward_local_slack, backward_local_slack, discount; /* Reset forward and backward weights for all tnodes. */ for (inode = 0; inode < num_tnodes; inode++) { tnode[inode].forward_weight = 0; tnode[inode].backward_weight = 0; } /* Set foward weights for each top-level tnode. */ num_at_level = tnodes_at_level[0].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[0].list[i]; tnode[inode].forward_weight = 1.; } /* Do a forward topological traversal to populate forward weights. */ for (ilevel = 0; ilevel < num_tnode_levels; ilevel++) { num_at_level = tnodes_at_level[ilevel].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; if (!(has_valid_T_arr(inode) && has_valid_T_req(inode))) { continue; } tedge = tnode[inode].out_edges; num_edges = tnode[inode].num_edges; for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; if (!(has_valid_T_arr(to_node) && has_valid_T_req(to_node))) { continue; } forward_local_slack = tnode[to_node].T_arr - tnode[inode].T_arr - tedge[iedge].Tdel; discount = pow((float) DISCOUNT_FUNCTION_BASE, -1 * forward_local_slack / criticality_denom); tnode[to_node].forward_weight += discount * tnode[inode].forward_weight; } } } /* Do a backward topological traversal to populate backward weights. Since the sinks are all on different levels, we have to check for them as we go. */ for (ilevel = num_tnode_levels - 1; ilevel >= 0; ilevel--) { num_at_level = tnodes_at_level[ilevel].nelem; for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[ilevel].list[i]; if (!(has_valid_T_arr(inode) && has_valid_T_req(inode))) { continue; } num_edges = tnode[inode].num_edges; if (num_edges == 0) { /* sink */ tnode[inode].backward_weight = 1.; } else { tedge = tnode[inode].out_edges; for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; if (!(has_valid_T_arr(to_node) && has_valid_T_req(to_node))) { continue; } backward_local_slack = tnode[to_node].T_req - tnode[inode].T_req - tedge[iedge].Tdel; discount = pow((float) DISCOUNT_FUNCTION_BASE, -1 * backward_local_slack / criticality_denom); tnode[inode].backward_weight += discount * tnode[to_node].backward_weight; } } } } } #endif static void update_slacks(t_slack * slacks, int source_clock_domain, int sink_clock_domain, float criticality_denom, boolean update_slack) { /* Updates the slack and criticality of each sink pin, or equivalently each edge, of each net. Go through the list of nets. If the net's driver tnode has been used, go through each tedge of that tnode and take the minimum of the slack/criticality for this traversal and the existing values. Since the criticality_denom can vary greatly between traversals, we have to update slack and criticality separately. Only update slack if we need to print it later (update_slack == TRUE). Note: there is a correspondence in indexing between out_edges and the net data structure: out_edges[iedge] = net[inet].node_block[iedge + 1] There is an offset of 1 because net[inet].node_block includes the driver node at index 0, while out_edges is part of the driver node and does not bother to refer to itself. */ int inet, iedge, inode, to_node, num_edges; t_tedge *tedge; float T_arr, Tdel, T_req, slk, timing_criticality; for (inet = 0; inet < num_timing_nets; inet++) { inode = f_net_to_driver_tnode[inet]; T_arr = tnode[inode].T_arr; if (!(has_valid_T_arr(inode) && has_valid_T_req(inode))) { continue; /* Only update this net on this traversal if its driver node has been updated on this traversal. */ } num_edges = tnode[inode].num_edges; tedge = tnode[inode].out_edges; for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; if (!(has_valid_T_arr(to_node) && has_valid_T_req(to_node))) { continue; /* Only update this edge on this traversal if this particular sink node has been updated on this traversal. */ } Tdel = tedge[iedge].Tdel; T_req = tnode[to_node].T_req; if (update_slack) { /* Update the slack for this edge. */ slk = T_req - T_arr - Tdel; if (slk < slacks->slack[inet][iedge + 1]) { /* Only update on this traversal if this edge would have lower slack from this traversal than its current value. */ slacks->slack[inet][iedge + 1] = slk; } } #if SLACK_DEFINITION == 'R' /* Since criticality_denom is not the same on each traversal, we have to update criticality separately. */ timing_criticality = 1 - (T_req - T_arr - Tdel)/criticality_denom; if (timing_criticality > slacks->timing_criticality[inet][iedge + 1]) { slacks->timing_criticality[inet][iedge + 1] = timing_criticality; } #ifdef PATH_COUNTING /* Also update path criticality separately. Kong uses slack / T_crit for the exponent, which is equivalent to criticality - 1. Depending on how PATH_COUNTING is defined, different functional forms are used. */ #if PATH_COUNTING == 'S' /* Use sum of forward and backward weights. */ slacks->path_criticality[inet][iedge + 1] = max(slacks->path_criticality[inet][iedge + 1], (tnode[inode].forward_weight + tnode[to_node].backward_weight) * pow((float) FINAL_DISCOUNT_FUNCTION_BASE, timing_criticality - 1)); #elif PATH_COUNTING == 'P' /* Use product of forward and backward weights. */ slacks->path_criticality[inet][iedge + 1] = max(slacks->path_criticality[inet][iedge + 1], tnode[inode].forward_weight * tnode[to_node].backward_weight * pow((float) FINAL_DISCOUNT_FUNCTION_BASE, timing_criticality - 1)); #elif PATH_COUNTING == 'L' /* Use natural log of product of forward and backward weights. */ slacks->path_criticality[inet][iedge + 1] = max(slacks->path_criticality[inet][iedge + 1], log(tnode[inode].forward_weight * tnode[to_node].backward_weight) * pow((float) FINAL_DISCOUNT_FUNCTION_BASE, timing_criticality - 1)); #elif PATH_COUNTING == 'R' /* Use product of natural logs of forward and backward weights. */ slacks->path_criticality[inet][iedge + 1] = max(slacks->path_criticality[inet][iedge + 1], log(tnode[inode].forward_weight) * log(tnode[to_node].backward_weight) * pow((float) FINAL_DISCOUNT_FUNCTION_BASE, timing_criticality - 1)); #endif #endif #endif } } } void print_lut_remapping(const char *fname) { FILE *fp; int inode, i; t_pb *pb; fp = my_fopen(fname, "w", 0); fprintf(fp, "# LUT_Name\tinput_pin_mapping\n"); for (inode = 0; inode < num_tnodes; inode++) { /* Print LUT input rebalancing */ if (tnode[inode].type == TN_PRIMITIVE_OPIN && tnode[inode].pb_graph_pin != NULL) { pb = block[tnode[inode].block].pb->rr_node_to_pb_mapping[tnode[inode].pb_graph_pin->pin_count_in_cluster]; if (pb != NULL && pb->lut_pin_remap != NULL) { assert(pb->pb_graph_node->pb_type->num_output_pins == 1 && pb->pb_graph_node->pb_type->num_clock_pins == 0); /* ensure LUT properties are valid */ assert(pb->pb_graph_node->num_input_ports == 1); fprintf(fp, "%s", pb->name); for (i = 0; i < pb->pb_graph_node->num_input_pins[0]; i++) { fprintf(fp, "\t%d", pb->lut_pin_remap[i]); } fprintf(fp, "\n"); } } } fclose(fp); } void print_critical_path(const char *fname) { /* Prints the critical path to a file. */ t_linked_int *critical_path_head, *critical_path_node; FILE *fp; int non_global_nets_on_crit_path, global_nets_on_crit_path; int tnodes_on_crit_path, inode, iblk, inet; e_tnode_type type; float total_net_delay, total_logic_delay, Tdel; critical_path_head = allocate_and_load_critical_path(); critical_path_node = critical_path_head; fp = my_fopen(fname, "w", 0); non_global_nets_on_crit_path = 0; global_nets_on_crit_path = 0; tnodes_on_crit_path = 0; total_net_delay = 0.; total_logic_delay = 0.; while (critical_path_node != NULL) { Tdel = print_critical_path_node(fp, critical_path_node); inode = critical_path_node->data; type = tnode[inode].type; tnodes_on_crit_path++; if (type == TN_CB_OPIN) { get_tnode_block_and_output_net(inode, &iblk, &inet); if (!timing_nets[inet].is_global) non_global_nets_on_crit_path++; else global_nets_on_crit_path++; total_net_delay += Tdel; } else { total_logic_delay += Tdel; } critical_path_node = critical_path_node->next; } fprintf(fp, "\nTnodes on critical path: %d Non-global nets on critical path: %d." "\n", tnodes_on_crit_path, non_global_nets_on_crit_path); fprintf(fp, "Global nets on critical path: %d.\n", global_nets_on_crit_path); fprintf(fp, "Total logic delay: %g (s) Total net delay: %g (s)\n", total_logic_delay, total_net_delay); vpr_printf(TIO_MESSAGE_INFO, "Nets on critical path: %d normal, %d global.\n", non_global_nets_on_crit_path, global_nets_on_crit_path); vpr_printf(TIO_MESSAGE_INFO, "Total logic delay: %g (s), total net delay: %g (s)\n", total_logic_delay, total_net_delay); /* Make sure total_logic_delay and total_net_delay add up to critical path delay,within 5 decimal places. */ assert(total_logic_delay + total_net_delay - get_critical_path_delay()/1e9 < 1e-5); fclose(fp); free_int_list(&critical_path_head); } t_linked_int * allocate_and_load_critical_path(void) { /* Finds the critical path and returns a list of the tnodes on the critical * * path in a linked list, from the path SOURCE to the path SINK. */ t_linked_int *critical_path_head, *curr_crit_node, *prev_crit_node; int inode, iedge, to_node, num_at_level_zero, i, j, crit_node = OPEN, num_edges; int source_clock_domain = UNDEFINED, sink_clock_domain = UNDEFINED; float min_slack = HUGE_POSITIVE_FLOAT, slack; t_tedge *tedge; /* If there's only one clock, we can use the arrival and required times currently on the timing graph to find the critical path. If there are multiple clocks, however, the values currently on the timing graph correspond to the last constraint (pair of clock domains) analysed, which may not be the constraint with the critical path. In this case, we have to find the constraint with the least slack and redo the timing analysis for this constraint so we get the right values onto the timing graph. */ if (g_sdc->num_constrained_clocks > 1) { /* The critical path belongs to the source and sink clock domains with the least slack. Find these clock domains now. */ for (i = 0; i < g_sdc->num_constrained_clocks; i++) { for (j = 0; j < g_sdc->num_constrained_clocks; j++) { if (min_slack > f_timing_stats->least_slack[i][j]) { min_slack = f_timing_stats->least_slack[i][j]; source_clock_domain = i; sink_clock_domain = j; } } } /* Do a timing analysis for this clock domain pair only. Set is_prepacked to FALSE since we don't care about the clusterer's normalized values. Set is_final_analysis to FALSE to get actual, rather than relaxed, slacks. Set max critical input/output paths to NULL since they aren't used unless is_prepacked is TRUE. */ do_timing_analysis_for_constraint(source_clock_domain, sink_clock_domain, FALSE, FALSE, (long*)NULL, (long*)NULL); } /* Start at the source (level-0) tnode with the least slack (T_req-T_arr). This will form the head of our linked list of tnodes on the critical path. */ min_slack = HUGE_POSITIVE_FLOAT; num_at_level_zero = tnodes_at_level[0].nelem; for (i = 0; i < num_at_level_zero; i++) { inode = tnodes_at_level[0].list[i]; if (has_valid_T_arr(inode) && has_valid_T_req(inode)) { /* Valid arrival and required times */ slack = tnode[inode].T_req - tnode[inode].T_arr; if (slack < min_slack) { crit_node = inode; min_slack = slack; } } } critical_path_head = (t_linked_int *) my_malloc(sizeof(t_linked_int)); critical_path_head->data = crit_node; assert(crit_node != OPEN); prev_crit_node = critical_path_head; num_edges = tnode[crit_node].num_edges; /* Keep adding the tnode in this tnode's fanout which has the least slack to our critical path linked list, then jump to that tnode and repeat, until we hit a tnode with no edges, which is the sink of the critical path. */ while (num_edges != 0) { curr_crit_node = (t_linked_int *) my_malloc(sizeof(t_linked_int)); prev_crit_node->next = curr_crit_node; tedge = tnode[crit_node].out_edges; min_slack = HUGE_POSITIVE_FLOAT; for (iedge = 0; iedge < num_edges; iedge++) { to_node = tedge[iedge].to_node; if (has_valid_T_arr(to_node) && has_valid_T_req(to_node)) { /* Valid arrival and required times */ slack = tnode[to_node].T_req - tnode[to_node].T_arr; if (slack < min_slack) { crit_node = to_node; min_slack = slack; } } } curr_crit_node->data = crit_node; prev_crit_node = curr_crit_node; num_edges = tnode[crit_node].num_edges; } prev_crit_node->next = NULL; return (critical_path_head); } void get_tnode_block_and_output_net(int inode, int *iblk_ptr, int *inet_ptr) { /* Returns the index of the block that this tnode is part of. If the tnode * * is a TN_CB_OPIN or TN_INPAD_OPIN (i.e. if it drives a net), the net index is * * returned via inet_ptr. Otherwise inet_ptr points at OPEN. */ int inet, ipin, iblk; e_tnode_type tnode_type; iblk = tnode[inode].block; tnode_type = tnode[inode].type; if (tnode_type == TN_CB_OPIN) { ipin = tnode[inode].pb_graph_pin->pin_count_in_cluster; inet = block[iblk].pb->rr_graph[ipin].net_num; assert(inet != OPEN); inet = vpack_to_clb_net_mapping[inet]; } else { inet = OPEN; } *iblk_ptr = iblk; *inet_ptr = inet; } void do_constant_net_delay_timing_analysis(t_timing_inf timing_inf, float constant_net_delay_value) { /* Does a timing analysis (simple) where it assumes that each net has a * * constant delay value. Used only when operation == TIMING_ANALYSIS_ONLY. */ /*struct s_linked_vptr *net_delay_chunk_list_head;*/ t_chunk net_delay_ch = {NULL, 0, NULL}; t_slack * slacks = NULL; float **net_delay = NULL; slacks = alloc_and_load_timing_graph(timing_inf); net_delay = alloc_net_delay(&net_delay_ch, timing_nets, num_timing_nets); load_constant_net_delay(net_delay, constant_net_delay_value, timing_nets, num_timing_nets); load_timing_graph_net_delays(net_delay); do_timing_analysis(slacks, FALSE, FALSE, TRUE); if (getEchoEnabled()) { if(isEchoFileEnabled(E_ECHO_CRITICAL_PATH)) print_critical_path("critical_path.echo"); if(isEchoFileEnabled(E_ECHO_TIMING_GRAPH)) print_timing_graph(getEchoFileName(E_ECHO_TIMING_GRAPH)); if(isEchoFileEnabled(E_ECHO_SLACK)) print_slack(slacks->slack, TRUE, getEchoFileName(E_ECHO_SLACK)); if(isEchoFileEnabled(E_ECHO_CRITICALITY)) print_criticality(slacks, TRUE, getEchoFileName(E_ECHO_CRITICALITY)); if(isEchoFileEnabled(E_ECHO_NET_DELAY)) print_net_delay(net_delay, getEchoFileName(E_ECHO_NET_DELAY)); } print_timing_stats(); free_timing_graph(slacks); free_net_delay(net_delay, &net_delay_ch); } #ifndef PATH_COUNTING static void update_normalized_costs(float criticality_denom, long max_critical_input_paths, long max_critical_output_paths) { int inode; /* Update the normalized costs for the clusterer. On each traversal, each cost is updated for tnodes analysed on this traversal if it would give this tnode a higher criticality when calculating block criticality for the clusterer. */ assert(criticality_denom != 0); /* Possible if timing analysis is being run pre-packing with all delays set to 0. This is not currently done, but if you're going to do it, you need to decide how best to normalize these values to suit your purposes. */ for (inode = 0; inode < num_tnodes; inode++) { /* Only calculate for tnodes which have valid arrival and required times. */ if (has_valid_T_arr(inode) && has_valid_T_req(inode)) { tnode[inode].prepacked_data->normalized_slack = std::min(tnode[inode].prepacked_data->normalized_slack, (tnode[inode].T_req - tnode[inode].T_arr)/criticality_denom); tnode[inode].prepacked_data->normalized_T_arr = std::max(tnode[inode].prepacked_data->normalized_T_arr, tnode[inode].T_arr/criticality_denom); tnode[inode].prepacked_data->normalized_total_critical_paths = std::max(tnode[inode].prepacked_data->normalized_total_critical_paths, ((float) tnode[inode].prepacked_data->num_critical_input_paths + tnode[inode].prepacked_data->num_critical_output_paths) / ((float) max_critical_input_paths + max_critical_output_paths)); } } } #endif /* Set new arrival time Special code for LUTs to enable LUT input delay balancing */ static void set_and_balance_arrival_time(int to_node, int from_node, float Tdel, boolean do_lut_input_balancing) { int i, j; t_pb *pb; boolean rebalance; t_tnode *input_tnode; boolean *assigned = NULL; int fastest_unassigned_pin, most_crit_tnode, most_crit_pin; float min_delay, highest_T_arr, balanced_T_arr; /* Normal case for determining arrival time */ tnode[to_node].T_arr = std::max(tnode[to_node].T_arr, tnode[from_node].T_arr + Tdel); /* Do LUT input rebalancing for LUTs */ if (do_lut_input_balancing && tnode[to_node].type == TN_PRIMITIVE_OPIN && tnode[to_node].pb_graph_pin != NULL) { pb = block[tnode[to_node].block].pb->rr_node_to_pb_mapping[tnode[to_node].pb_graph_pin->pin_count_in_cluster]; if (pb != NULL && pb->lut_pin_remap != NULL) { /* this is a LUT primitive, do pin swapping */ assert(pb->pb_graph_node->pb_type->num_output_pins == 1 && pb->pb_graph_node->pb_type->num_clock_pins == 0); /* ensure LUT properties are valid */ assert(pb->pb_graph_node->num_input_ports == 1); assert(tnode[from_node].block == tnode[to_node].block); /* assign from_node to default location */ assert(pb->lut_pin_remap[tnode[from_node].pb_graph_pin->pin_number] == OPEN); pb->lut_pin_remap[tnode[from_node].pb_graph_pin->pin_number] = tnode[from_node].pb_graph_pin->pin_number; /* If all input pins are known, perform LUT input delay rebalancing, do nothing otherwise */ rebalance = TRUE; for (i = 0; i < pb->pb_graph_node->num_input_pins[0]; i++) { input_tnode = block[tnode[to_node].block].pb->rr_graph[pb->pb_graph_node->input_pins[0][i].pin_count_in_cluster].tnode; if (input_tnode != NULL && pb->lut_pin_remap[i] == OPEN) { rebalance = FALSE; } } if (rebalance == TRUE) { /* Rebalance LUT inputs so that the most critical paths get the fastest inputs */ balanced_T_arr = OPEN; assigned = (boolean*)my_calloc(pb->pb_graph_node->num_input_pins[0], sizeof(boolean)); /* Clear pin remapping */ for (i = 0; i < pb->pb_graph_node->num_input_pins[0]; i++) { pb->lut_pin_remap[i] = OPEN; } /* load new T_arr and pin mapping */ for (i = 0; i < pb->pb_graph_node->num_input_pins[0]; i++) { /* Find fastest physical input pin of LUT */ fastest_unassigned_pin = OPEN; min_delay = OPEN; for (j = 0; j < pb->pb_graph_node->num_input_pins[0]; j++) { if (pb->lut_pin_remap[j] == OPEN) { if (fastest_unassigned_pin == OPEN) { fastest_unassigned_pin = j; min_delay = pb->pb_graph_node->input_pins[0][j].pin_timing_del_max[0]; } else if (min_delay > pb->pb_graph_node->input_pins[0][j].pin_timing_del_max[0]) { fastest_unassigned_pin = j; min_delay = pb->pb_graph_node->input_pins[0][j].pin_timing_del_max[0]; } } } assert(fastest_unassigned_pin != OPEN); /* Find most critical LUT input pin in user circuit */ most_crit_tnode = OPEN; highest_T_arr = OPEN; most_crit_pin = OPEN; for (j = 0; j < pb->pb_graph_node->num_input_pins[0]; j++) { input_tnode = block[tnode[to_node].block].pb->rr_graph[pb->pb_graph_node->input_pins[0][j].pin_count_in_cluster].tnode; if (input_tnode != NULL && assigned[j] == FALSE) { if (most_crit_tnode == OPEN) { most_crit_tnode = get_tnode_index(input_tnode); highest_T_arr = input_tnode->T_arr; most_crit_pin = j; } else if (highest_T_arr < input_tnode->T_arr) { most_crit_tnode = get_tnode_index(input_tnode); highest_T_arr = input_tnode->T_arr; most_crit_pin = j; } } } if (most_crit_tnode == OPEN) { break; } else { assert(tnode[most_crit_tnode].num_edges == 1); tnode[most_crit_tnode].out_edges[0].Tdel = min_delay; pb->lut_pin_remap[fastest_unassigned_pin] = most_crit_pin; assigned[most_crit_pin] = TRUE; if (balanced_T_arr < min_delay + highest_T_arr) { balanced_T_arr = min_delay + highest_T_arr; } } } free(assigned); if (balanced_T_arr != OPEN) { tnode[to_node].T_arr = balanced_T_arr; } } } } } static void load_clock_domain_and_clock_and_io_delay(boolean is_prepacked) { /* Loads clock domain and clock delay onto TN_FF_SOURCE and TN_FF_SINK tnodes. The clock domain of each clock is its index in g_sdc->constrained_clocks. We do this by matching each clock input pad to a constrained clock name, then propagating forward its domain index to all flip-flops fed by it (TN_FF_CLOCK tnodes), then later looking up the TN_FF_CLOCK tnode corresponding to every TN_FF_SOURCE and TN_FF_SINK tnode. We also add up the delays along the clock net to each TN_FF_CLOCK tnode to give it (and the SOURCE/SINK nodes) a clock delay. Also loads input delay/output delay (from set_input_delay or set_output_delay SDC constraints) onto the tedges between TN_INPAD_SOURCE/OPIN and TN_OUTPAD_IPIN/SINK tnodes. Finds fanout of each clock domain, including virtual (external) clocks. Marks unconstrained I/Os with a dummy clock domain (-1). */ int i, iclock, inode, num_at_level, clock_index, input_index, output_index; char * net_name; t_tnode * clock_node; /* Wipe fanout of each clock domain in g_sdc->constrained_clocks. */ for (iclock = 0; iclock < g_sdc->num_constrained_clocks; iclock++) { g_sdc->constrained_clocks[iclock].fanout = 0; } /* First, visit all TN_INPAD_SOURCE tnodes */ num_at_level = tnodes_at_level[0].nelem; /* There are num_at_level top-level tnodes. */ for (i = 0; i < num_at_level; i++) { inode = tnodes_at_level[0].list[i]; /* Iterate through each tnode. inode is the index of the tnode in the array tnode. */ if (tnode[inode].type == TN_INPAD_SOURCE) { /* See if this node is the start of an I/O pad (as oppposed to a flip-flop source). */ net_name = find_tnode_net_name(inode, is_prepacked); if ((clock_index = find_clock(net_name)) != -1) { /* We have a clock inpad. */ /* Set clock skew to 0 at the source and propagate skew recursively to all connected nodes, adding delay as we go. Set the clock domain to the index of the clock in the g_sdc->constrained_clocks array and propagate unchanged. */ tnode[inode].clock_delay = 0.; tnode[inode].clock_domain = clock_index; propagate_clock_domain_and_skew(inode); /* Set the clock domain of this clock inpad to -1, so that we do not analyse it. If we did not do this, the clock net would be analysed on the same iteration of the timing analyzer as the flip-flops it drives! */ tnode[inode].clock_domain = -1; } else if ((input_index = find_input(net_name)) != -1) { /* We have a constrained non-clock inpad - find the clock it's constrained on. */ clock_index = find_clock(g_sdc->constrained_inputs[input_index].clock_name); assert(clock_index != -1); /* The clock domain for this input is that of its virtual clock */ tnode[inode].clock_domain = clock_index; /* Increment the fanout of this virtual clock domain. */ g_sdc->constrained_clocks[clock_index].fanout++; /* Mark input delay specified in SDC file on the timing graph edge leading out from the TN_INPAD_SOURCE node. */ tnode[inode].out_edges[0].Tdel = g_sdc->constrained_inputs[input_index].delay * 1e-9; /* normalize to be in seconds not ns */ } else { /* We have an unconstrained input - mark with dummy clock domain and do not analyze. */ tnode[inode].clock_domain = -1; } } } /* Second, visit all TN_OUTPAD_SINK tnodes. Unlike for TN_INPAD_SOURCE tnodes, we have to search the entire tnode array since these are all on different levels. */ for (inode = 0; inode < num_tnodes; inode++) { if (tnode[inode].type == TN_OUTPAD_SINK) { /* Since the pb_graph_pin of TN_OUTPAD_SINK tnodes points to NULL, we have to find the net from the pb_graph_pin of the corresponding TN_OUTPAD_IPIN node. Exploit the fact that the TN_OUTPAD_IPIN node will always be one prior in the tnode array. */ assert(tnode[inode - 1].type == TN_OUTPAD_IPIN); net_name = find_tnode_net_name(inode, is_prepacked); output_index = find_output(net_name + 4); /* the + 4 removes the prefix "out:" automatically prepended to outputs */ if (output_index != -1) { /* We have a constrained outpad, find the clock it's constrained on. */ clock_index = find_clock(g_sdc->constrained_outputs[output_index].clock_name); assert(clock_index != -1); /* The clock doain for this output is that of its virtual clock */ tnode[inode].clock_domain = clock_index; /* Increment the fanout of this virtual clock domain. */ g_sdc->constrained_clocks[clock_index].fanout++; /* Mark output delay specified in SDC file on the timing graph edge leading into the TN_OUTPAD_SINK node. However, this edge is part of the corresponding TN_OUTPAD_IPIN node. Exploit the fact that the TN_OUTPAD_IPIN node will always be one prior in the tnode array. */ tnode[inode - 1].out_edges[0].Tdel = g_sdc->constrained_outputs[output_index].delay * 1e-9; /* normalize to be in seconds not ns */ } else { /* We have an unconstrained input - mark with dummy clock domain and do not analyze. */ tnode[inode].clock_domain = -1; } } } /* Third, visit all TN_FF_SOURCE and TN_FF_SINK tnodes, and transfer the clock domain and skew from their corresponding TN_FF_CLOCK tnodes*/ for (inode = 0; inode < num_tnodes; inode++) { if (tnode[inode].type == TN_FF_SOURCE || tnode[inode].type == TN_FF_SINK) { clock_node = find_ff_clock_tnode(inode, is_prepacked); tnode[inode].clock_domain = clock_node->clock_domain; tnode[inode].clock_delay = clock_node->clock_delay; } } } static void propagate_clock_domain_and_skew(int inode) { /* Given a tnode indexed by inode (which is part of a clock net), * propagate forward the clock domain (unchanged) and skew (adding the delay of edges) to all nodes in its fanout. * We then call recursively on all children in a depth-first search. If num_edges is 0, we should be at an TN_FF_CLOCK tnode; we then set the * TN_FF_SOURCE and TN_FF_SINK nodes to have the same clock domain and skew as the TN_FF_CLOCK node. We implicitly rely on a tnode not being * part of two separate clock nets, since undefined behaviour would result if one DFS overwrote the results of another. This may * be problematic in cases of multiplexed or locally-generated clocks. */ int iedge, to_node; t_tedge * tedge; tedge = tnode[inode].out_edges; /* Get the list of edges from the node we're visiting. */ if (!tedge) { /* Leaf/sink node; base case of the recursion. */ assert(tnode[inode].type == TN_FF_CLOCK); assert(tnode[inode].clock_domain != -1); /* Make sure clock domain is valid. */ g_sdc->constrained_clocks[tnode[inode].clock_domain].fanout++; return; } for (iedge = 0; iedge < tnode[inode].num_edges; iedge++) { /* Go through each edge coming out from this tnode */ to_node = tedge[iedge].to_node; /* Propagate clock skew forward along this clock net, adding the delay of the wires (edges) of the clock network. */ tnode[to_node].clock_delay = tnode[inode].clock_delay + tedge[iedge].Tdel; /* Propagate clock domain forward unchanged */ tnode[to_node].clock_domain = tnode[inode].clock_domain; /* Finally, call recursively on the destination tnode. */ propagate_clock_domain_and_skew(to_node); } } static char * find_tnode_net_name(int inode, boolean is_prepacked) { /* Finds the name of the net which a tnode (inode) is on (different for pre-/post-packed netlists). */ int logic_block; /* Name chosen not to conflict with the array logical_block */ char * net_name; t_pb * physical_block; t_pb_graph_pin * pb_graph_pin; logic_block = tnode[inode].block; if (is_prepacked) { net_name = logical_block[logic_block].name; } else { physical_block = block[logic_block].pb; pb_graph_pin = tnode[inode].pb_graph_pin; net_name = physical_block->rr_node_to_pb_mapping[pb_graph_pin->pin_count_in_cluster]->name; } return net_name; } static t_tnode * find_ff_clock_tnode(int inode, boolean is_prepacked) { /* Finds the TN_FF_CLOCK tnode on the same flipflop as an TN_FF_SOURCE or TN_FF_SINK tnode. */ int logic_block; /* Name chosen not to conflict with the array logical_block */ t_tnode * ff_clock_tnode; t_rr_node * rr_graph; t_pb_graph_node * parent_pb_graph_node; t_pb_graph_pin * ff_source_or_sink_pb_graph_pin, * clock_pb_graph_pin; logic_block = tnode[inode].block; if (is_prepacked) { ff_clock_tnode = logical_block[logic_block].clock_net_tnode; } else { rr_graph = block[logic_block].pb->rr_graph; ff_source_or_sink_pb_graph_pin = tnode[inode].pb_graph_pin; parent_pb_graph_node = ff_source_or_sink_pb_graph_pin->parent_node; /* Make sure there's only one clock port and only one clock pin in that port */ assert(parent_pb_graph_node->num_clock_ports == 1); assert(parent_pb_graph_node->num_clock_pins[0] == 1); clock_pb_graph_pin = &parent_pb_graph_node->clock_pins[0][0]; ff_clock_tnode = rr_graph[clock_pb_graph_pin->pin_count_in_cluster].tnode; } assert(ff_clock_tnode != NULL); assert(ff_clock_tnode->type == TN_FF_CLOCK); return ff_clock_tnode; } static int find_clock(char * net_name) { /* Given a string net_name, find whether it's the name of a clock in the array g_sdc->constrained_clocks. * * if it is, return the clock's index in g_sdc->constrained_clocks; if it's not, return -1. */ int index; for (index = 0; index < g_sdc->num_constrained_clocks; index++) { if (strcmp(net_name, g_sdc->constrained_clocks[index].name) == 0) { return index; } } return -1; } static int find_input(char * net_name) { /* Given a string net_name, find whether it's the name of a constrained input in the array g_sdc->constrained_inputs. * * if it is, return its index in g_sdc->constrained_inputs; if it's not, return -1. */ int index; for (index = 0; index < g_sdc->num_constrained_inputs; index++) { if (strcmp(net_name, g_sdc->constrained_inputs[index].name) == 0) { return index; } } return -1; } static int find_output(char * net_name) { /* Given a string net_name, find whether it's the name of a constrained output in the array g_sdc->constrained_outputs. * * if it is, return its index in g_sdc->constrained_outputs; if it's not, return -1. */ int index; for (index = 0; index < g_sdc->num_constrained_outputs; index++) { if (strcmp(net_name, g_sdc->constrained_outputs[index].name) == 0) { return index; } } return -1; } static int find_cf_constraint(char * source_clock_name, char * sink_ff_name) { /* Given a source clock domain and a sink flip-flop, find out if there's an override constraint between them. If there is, return the index in g_sdc->cf_constraints; if there is not, return -1. */ int icf, isource, isink; for (icf = 0; icf < g_sdc->num_cf_constraints; icf++) { for (isource = 0; isource < g_sdc->cf_constraints[icf].num_source; isource++) { if (strcmp(g_sdc->cf_constraints[icf].source_list[isource], source_clock_name) == 0) { for (isink = 0; isink < g_sdc->cf_constraints[icf].num_sink; isink++) { if (strcmp(g_sdc->cf_constraints[icf].sink_list[isink], sink_ff_name) == 0) { return icf; } } } } } return -1; } static inline int get_tnode_index(t_tnode * node) { /* Returns the index of pointer_to_tnode in the array tnode [0..num_tnodes - 1] using pointer arithmetic. */ return node - tnode; } static inline boolean has_valid_T_arr(int inode) { /* Has this tnode's arrival time been changed from its original value of HUGE_NEGATIVE_FLOAT? */ return (boolean) (tnode[inode].T_arr > HUGE_NEGATIVE_FLOAT + 1); } static inline boolean has_valid_T_req(int inode) { /* Has this tnode's required time been changed from its original value of HUGE_POSITIVE_FLOAT? */ return (boolean) (tnode[inode].T_req < HUGE_POSITIVE_FLOAT - 1); } #ifndef PATH_COUNTING boolean has_valid_normalized_T_arr(int inode) { /* Has this tnode's normalized_T_arr been changed from its original value of HUGE_NEGATIVE_FLOAT? */ return (boolean) (tnode[inode].prepacked_data->normalized_T_arr > HUGE_NEGATIVE_FLOAT + 1); } #endif float get_critical_path_delay(void) { /* Finds the critical path delay, which is the minimum clock period required to meet the constraint corresponding to the pair of source and sink clock domains with the least slack in the design. */ int source_clock_domain, sink_clock_domain; float least_slack_in_design = HUGE_POSITIVE_FLOAT, critical_path_delay = UNDEFINED; if (!g_sdc) return UNDEFINED; /* If timing analysis is off, for instance. */ for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (least_slack_in_design > f_timing_stats->least_slack[source_clock_domain][sink_clock_domain]) { least_slack_in_design = f_timing_stats->least_slack[source_clock_domain][sink_clock_domain]; critical_path_delay = f_timing_stats->cpd[source_clock_domain][sink_clock_domain]; } } } return critical_path_delay * 1e9; /* Convert to nanoseconds */ } void print_timing_stats(void) { /* Prints critical path delay/fmax, least slack in design, and, for multiple-clock designs, minimum required clock period to meet each constraint, least slack per constraint, geometric average clock frequency, and fanout-weighted geometric average clock frequency. */ int source_clock_domain, sink_clock_domain, clock_domain, fanout, total_fanout = 0, num_netlist_clocks_with_intra_domain_paths = 0; float geomean_period = 1., least_slack_in_design = HUGE_POSITIVE_FLOAT, critical_path_delay = UNDEFINED; double fanout_weighted_geomean_period = 1.; boolean found; /* Find critical path delay. If the pb_max_internal_delay is greater than this, it becomes the limiting factor on critical path delay, so print that instead, with a special message. */ for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (least_slack_in_design > f_timing_stats->least_slack[source_clock_domain][sink_clock_domain]) { least_slack_in_design = f_timing_stats->least_slack[source_clock_domain][sink_clock_domain]; critical_path_delay = f_timing_stats->cpd[source_clock_domain][sink_clock_domain]; } } } if (pb_max_internal_delay != UNDEFINED && pb_max_internal_delay > critical_path_delay) { critical_path_delay = pb_max_internal_delay; vpr_printf(TIO_MESSAGE_INFO, "Final critical path: %g ns\n", 1e9 * critical_path_delay); vpr_printf(TIO_MESSAGE_INFO, "\t(capped by fmax of block type %s)\n", pbtype_max_internal_delay->name); } else { vpr_printf(TIO_MESSAGE_INFO, "Final critical path: %g ns\n", 1e9 * critical_path_delay); } if (g_sdc->num_constrained_clocks <= 1) { /* Although critical path delay is always well-defined, it doesn't make sense to talk about fmax for multi-clock circuits */ vpr_printf(TIO_MESSAGE_INFO, "f_max: %g MHz\n", 1e-6 / critical_path_delay); } /* Also print the least slack in the design */ vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Least slack in design: %g ns\n", 1e9 * least_slack_in_design); vpr_printf(TIO_MESSAGE_INFO, "\n"); if (g_sdc->num_constrained_clocks > 1) { /* Multiple-clock design */ /* Print minimum possible clock period to meet each constraint. Convert to nanoseconds. */ vpr_printf(TIO_MESSAGE_INFO, "Minimum possible clock period to meet each constraint (including skew effects):\n"); for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { /* Print the intra-domain constraint if it was analysed. */ if (g_sdc->domain_constraint[source_clock_domain][source_clock_domain] > NEGATIVE_EPSILON) { vpr_printf(TIO_MESSAGE_INFO, "%s to %s: %g ns (%g MHz)\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[source_clock_domain].name, 1e9 * f_timing_stats->cpd[source_clock_domain][source_clock_domain], 1e-6 / f_timing_stats->cpd[source_clock_domain][source_clock_domain]); } else { vpr_printf(TIO_MESSAGE_INFO, "%s to %s: --\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[source_clock_domain].name); } /* Then, print all other constraints on separate lines, indented. We re-print the source clock domain's name so there's no ambiguity when parsing. */ for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (source_clock_domain == sink_clock_domain) continue; /* already done that */ if (g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] > NEGATIVE_EPSILON) { /* If this domain pair was analysed */ vpr_printf(TIO_MESSAGE_INFO, "\t%s to %s: %g ns (%g MHz)\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[sink_clock_domain].name, 1e9 * f_timing_stats->cpd[source_clock_domain][sink_clock_domain], 1e-6 / f_timing_stats->cpd[source_clock_domain][sink_clock_domain]); } else { vpr_printf(TIO_MESSAGE_INFO, "\t%s to %s: --\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[sink_clock_domain].name); } } } /* Print least slack per constraint. */ vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Least slack per constraint:\n"); for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { /* Print the intra-domain slack if valid. */ if (f_timing_stats->least_slack[source_clock_domain][source_clock_domain] < HUGE_POSITIVE_FLOAT - 1) { vpr_printf(TIO_MESSAGE_INFO, "%s to %s: %g ns\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[source_clock_domain].name, 1e9 * f_timing_stats->least_slack[source_clock_domain][source_clock_domain]); } else { vpr_printf(TIO_MESSAGE_INFO, "%s to %s: --\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[source_clock_domain].name); } /* Then, print all other slacks on separate lines. */ for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { if (source_clock_domain == sink_clock_domain) continue; /* already done that */ if (f_timing_stats->least_slack[source_clock_domain][sink_clock_domain] < HUGE_POSITIVE_FLOAT - 1) { /* If this domain pair was analysed and has a valid slack */ vpr_printf(TIO_MESSAGE_INFO, "\t%s to %s: %g ns\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[sink_clock_domain].name, 1e9 * f_timing_stats->least_slack[source_clock_domain][sink_clock_domain]); } else { vpr_printf(TIO_MESSAGE_INFO, "\t%s to %s: --\n", g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[sink_clock_domain].name); } } } /* Calculate geometric mean f_max (fanout-weighted and unweighted) from the diagonal (intra-domain) entries of critical_path_delay, excluding domains without intra-domain paths (for which the timing constraint is DO_NOT_ANALYSE) and virtual clocks. */ found = FALSE; for (clock_domain = 0; clock_domain < g_sdc->num_constrained_clocks; clock_domain++) { if (g_sdc->domain_constraint[clock_domain][clock_domain] > NEGATIVE_EPSILON && g_sdc->constrained_clocks[clock_domain].is_netlist_clock) { geomean_period *= f_timing_stats->cpd[clock_domain][clock_domain]; fanout = g_sdc->constrained_clocks[clock_domain].fanout; fanout_weighted_geomean_period *= pow((double) f_timing_stats->cpd[clock_domain][clock_domain], fanout); total_fanout += fanout; num_netlist_clocks_with_intra_domain_paths++; found = TRUE; } } if (found) { /* Only print these if we found at least one clock domain with intra-domain paths. */ geomean_period = pow(geomean_period, (float) 1/num_netlist_clocks_with_intra_domain_paths); fanout_weighted_geomean_period = pow(fanout_weighted_geomean_period, (double) 1/total_fanout); /* Convert to MHz */ vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Geometric mean intra-domain period: %g ns (%g MHz)\n", 1e9 * geomean_period, 1e-6 / geomean_period); vpr_printf(TIO_MESSAGE_INFO, "Fanout-weighted geomean intra-domain period: %g ns (%g MHz)\n", 1e9 * fanout_weighted_geomean_period, 1e-6 / fanout_weighted_geomean_period); } vpr_printf(TIO_MESSAGE_INFO, "\n"); } } static void print_timing_constraint_info(const char *fname) { /* Prints the contents of g_sdc->domain_constraint, g_sdc->constrained_clocks, constrained_ios and g_sdc->cc_constraints to a file. */ FILE * fp; int source_clock_domain, sink_clock_domain, i, j; int * clock_name_length = (int *) my_malloc(g_sdc->num_constrained_clocks * sizeof(int)); /* Array of clock name lengths */ int max_clock_name_length = INT_MIN; char * clock_name; fp = my_fopen(fname, "w", 0); /* Get lengths of each clock name and max length so we can space the columns properly. */ for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { clock_name = g_sdc->constrained_clocks[sink_clock_domain].name; clock_name_length[sink_clock_domain] = strlen(clock_name); if (clock_name_length[sink_clock_domain] > max_clock_name_length) { max_clock_name_length = clock_name_length[sink_clock_domain]; } } /* First, combine info from g_sdc->domain_constraint and g_sdc->constrained_clocks into a matrix (they're indexed the same as each other). */ fprintf(fp, "Timing constraints in ns (source clock domains down left side, sink along top).\n" "A value of -1.00 means the pair of source and sink domains will not be analysed.\n\n"); print_spaces(fp, max_clock_name_length + 4); for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { fprintf(fp, "%s", g_sdc->constrained_clocks[sink_clock_domain].name); /* Minimum column width of 8 */ print_spaces(fp, std::max(8 - clock_name_length[sink_clock_domain], 4)); } fprintf(fp, "\n"); for (source_clock_domain = 0; source_clock_domain < g_sdc->num_constrained_clocks; source_clock_domain++) { fprintf(fp, "%s", g_sdc->constrained_clocks[source_clock_domain].name); print_spaces(fp, max_clock_name_length + 4 - clock_name_length[source_clock_domain]); for (sink_clock_domain = 0; sink_clock_domain < g_sdc->num_constrained_clocks; sink_clock_domain++) { fprintf(fp, "%5.2f", g_sdc->domain_constraint[source_clock_domain][sink_clock_domain]); /* Minimum column width of 8 */ print_spaces(fp, std::max(clock_name_length[sink_clock_domain] - 1, 3)); } fprintf(fp, "\n"); } free(clock_name_length); /* Second, print I/O constraints. */ fprintf(fp, "\nList of constrained inputs (note: constraining a clock net input has no effect):\n"); for (i = 0; i < g_sdc->num_constrained_inputs; i++) { fprintf(fp, "Input name %s on clock %s with input delay %.2f ns\n", g_sdc->constrained_inputs[i].name, g_sdc->constrained_inputs[i].clock_name, g_sdc->constrained_inputs[i].delay); } fprintf(fp, "\nList of constrained outputs:\n"); for (i = 0; i < g_sdc->num_constrained_outputs; i++) { fprintf(fp, "Output name %s on clock %s with output delay %.2f ns\n", g_sdc->constrained_outputs[i].name, g_sdc->constrained_outputs[i].clock_name, g_sdc->constrained_outputs[i].delay); } /* Third, print override constraints. */ fprintf(fp, "\nList of override constraints (non-default constraints created by set_false_path, set_clock_groups, \nset_max_delay, and set_multicycle_path):\n"); for (i = 0; i < g_sdc->num_cc_constraints; i++) { fprintf(fp, "Clock domain"); for (j = 0; j < g_sdc->cc_constraints[i].num_source; j++) { fprintf(fp, " %s,", g_sdc->cc_constraints[i].source_list[j]); } fprintf(fp, " to clock domain"); for (j = 0; j < g_sdc->cc_constraints[i].num_sink - 1; j++) { fprintf(fp, " %s,", g_sdc->cc_constraints[i].sink_list[j]); } /* We have to print the last one separately because we don't want a comma after it. */ if (g_sdc->cc_constraints[i].num_multicycles == 0) { /* not a multicycle constraint */ fprintf(fp, " %s: %.2f ns\n", g_sdc->cc_constraints[i].sink_list[j], g_sdc->cc_constraints[i].constraint); } else { /* multicycle constraint */ fprintf(fp, " %s: %d multicycles\n", g_sdc->cc_constraints[i].sink_list[j], g_sdc->cc_constraints[i].num_multicycles); } } for (i = 0; i < g_sdc->num_cf_constraints; i++) { fprintf(fp, "Clock domain"); for (j = 0; j < g_sdc->cf_constraints[i].num_source; j++) { fprintf(fp, " %s,", g_sdc->cf_constraints[i].source_list[j]); } fprintf(fp, " to flip-flop"); for (j = 0; j < g_sdc->cf_constraints[i].num_sink - 1; j++) { fprintf(fp, " %s,", g_sdc->cf_constraints[i].sink_list[j]); } /* We have to print the last one separately because we don't want a comma after it. */ if (g_sdc->cf_constraints[i].num_multicycles == 0) { /* not a multicycle constraint */ fprintf(fp, " %s: %.2f ns\n", g_sdc->cf_constraints[i].sink_list[j], g_sdc->cf_constraints[i].constraint); } else { /* multicycle constraint */ fprintf(fp, " %s: %d multicycles\n", g_sdc->cf_constraints[i].sink_list[j], g_sdc->cf_constraints[i].num_multicycles); } } for (i = 0; i < g_sdc->num_fc_constraints; i++) { fprintf(fp, "Flip-flop"); for (j = 0; j < g_sdc->fc_constraints[i].num_source; j++) { fprintf(fp, " %s,", g_sdc->fc_constraints[i].source_list[j]); } fprintf(fp, " to clock domain"); for (j = 0; j < g_sdc->fc_constraints[i].num_sink - 1; j++) { fprintf(fp, " %s,", g_sdc->fc_constraints[i].sink_list[j]); } /* We have to print the last one separately because we don't want a comma after it. */ if (g_sdc->fc_constraints[i].num_multicycles == 0) { /* not a multicycle constraint */ fprintf(fp, " %s: %.2f ns\n", g_sdc->fc_constraints[i].sink_list[j], g_sdc->fc_constraints[i].constraint); } else { /* multicycle constraint */ fprintf(fp, " %s: %d multicycles\n", g_sdc->fc_constraints[i].sink_list[j], g_sdc->fc_constraints[i].num_multicycles); } } for (i = 0; i < g_sdc->num_ff_constraints; i++) { fprintf(fp, "Flip-flop"); for (j = 0; j < g_sdc->ff_constraints[i].num_source; j++) { fprintf(fp, " %s,", g_sdc->ff_constraints[i].source_list[j]); } fprintf(fp, " to flip-flop"); for (j = 0; j < g_sdc->ff_constraints[i].num_sink - 1; j++) { fprintf(fp, " %s,", g_sdc->ff_constraints[i].sink_list[j]); } /* We have to print the last one separately because we don't want a comma after it. */ if (g_sdc->ff_constraints[i].num_multicycles == 0) { /* not a multicycle constraint */ fprintf(fp, " %s: %.2f ns\n", g_sdc->ff_constraints[i].sink_list[j], g_sdc->ff_constraints[i].constraint); } else { /* multicycle constraint */ fprintf(fp, " %s: %d multicycles\n", g_sdc->ff_constraints[i].sink_list[j], g_sdc->ff_constraints[i].num_multicycles); } } fclose(fp); } static void print_spaces(FILE * fp, int num_spaces) { /* Prints num_spaces spaces to file pointed to by fp. */ for ( ; num_spaces > 0; num_spaces--) { fprintf(fp, " "); } } void print_timing_graph_as_blif (const char *fname, t_model *models) { struct s_model_ports *port; struct s_linked_vptr *p_io_removed; /* Prints out the critical path to a file. */ FILE *fp; int i, j; fp = my_fopen(fname, "w", 0); fprintf(fp, ".model %s\n", blif_circuit_name); fprintf(fp, ".inputs "); for (i = 0; i < num_logical_blocks; i++) { if (logical_block[i].type == VPACK_INPAD) { fprintf(fp, "\\\n%s ", logical_block[i].name); } } p_io_removed = circuit_p_io_removed; while (p_io_removed) { fprintf(fp, "\\\n%s ", (char *) p_io_removed->data_vptr); p_io_removed = p_io_removed->next; } fprintf(fp, "\n"); fprintf(fp, ".outputs "); for (i = 0; i < num_logical_blocks; i++) { if (logical_block[i].type == VPACK_OUTPAD) { /* Outputs have a "out:" prepended to them, must remove */ fprintf(fp, "\\\n%s ", &logical_block[i].name[4]); } } fprintf(fp, "\n"); fprintf(fp, ".names unconn\n"); fprintf(fp, " 0\n\n"); /* Print out primitives */ for (i = 0; i < num_logical_blocks; i++) { print_primitive_as_blif (fp, i); } /* Print out tnode connections */ for (i = 0; i < num_tnodes; i++) { if (tnode[i].type != TN_PRIMITIVE_IPIN && tnode[i].type != TN_FF_SOURCE && tnode[i].type != TN_INPAD_SOURCE && tnode[i].type != TN_OUTPAD_IPIN) { for (j = 0; j < tnode[i].num_edges; j++) { fprintf(fp, ".names tnode_%d tnode_%d\n", i, tnode[i].out_edges[j].to_node); fprintf(fp, "1 1\n\n"); } } } fprintf(fp, ".end\n\n"); /* Print out .subckt models */ while (models) { fprintf(fp, ".model %s\n", models->name); fprintf(fp, ".inputs "); port = models->inputs; while (port) { if (port->size > 1) { for (j = 0; j < port->size; j++) { fprintf(fp, "%s[%d] ", port->name, j); } } else { fprintf(fp, "%s ", port->name); } port = port->next; } fprintf(fp, "\n"); fprintf(fp, ".outputs "); port = models->outputs; while (port) { if (port->size > 1) { for (j = 0; j < port->size; j++) { fprintf(fp, "%s[%d] ", port->name, j); } } else { fprintf(fp, "%s ", port->name); } port = port->next; } fprintf(fp, "\n.blackbox\n.end\n\n"); fprintf(fp, "\n\n"); models = models->next; } fclose(fp); } static void print_primitive_as_blif (FILE *fpout, int iblk) { int i, j; struct s_model_ports *port; struct s_linked_vptr *truth_table; t_rr_node *irr_graph; t_pb_graph_node *pb_graph_node; int node; /* Print primitives found in timing graph in blif format based on whether this is a logical primitive or a physical primitive */ if (logical_block[iblk].type == VPACK_INPAD) { if (logical_block[iblk].pb == NULL) { fprintf(fpout, ".names %s tnode_%d\n", logical_block[iblk].name, get_tnode_index(logical_block[iblk].output_net_tnodes[0][0])); } else { fprintf(fpout, ".names %s tnode_%d\n", logical_block[iblk].name, get_tnode_index(logical_block[iblk].pb->rr_graph[logical_block[iblk].pb->pb_graph_node->output_pins[0][0].pin_count_in_cluster].tnode)); } fprintf(fpout, "1 1\n\n"); } else if (logical_block[iblk].type == VPACK_OUTPAD) { /* outputs have the symbol out: automatically prepended to it, must remove */ if (logical_block[iblk].pb == NULL) { fprintf(fpout, ".names tnode_%d %s\n", get_tnode_index(logical_block[iblk].input_net_tnodes[0][0]), &logical_block[iblk].name[4]); } else { /* avoid the out: from output pad naming */ fprintf(fpout, ".names tnode_%d %s\n", get_tnode_index(logical_block[iblk].pb->rr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][0].pin_count_in_cluster].tnode), (logical_block[iblk].name + 4)); } fprintf(fpout, "1 1\n\n"); } else if (strcmp(logical_block[iblk].model->name, "latch") == 0) { fprintf(fpout, ".latch "); node = OPEN; if (logical_block[iblk].pb == NULL) { i = 0; port = logical_block[iblk].model->inputs; while (port) { if (!port->is_clock) { assert(port->size == 1); for (j = 0; j < port->size; j++) { if (logical_block[iblk].input_net_tnodes[i][j] != NULL) { fprintf(fpout, "tnode_%d ", get_tnode_index(logical_block[iblk].input_net_tnodes[i][j])); } else { assert(0); } } i++; } port = port->next; } assert(i == 1); i = 0; port = logical_block[iblk].model->outputs; while (port) { assert(port->size == 1); for (j = 0; j < port->size; j++) { if (logical_block[iblk].output_net_tnodes[i][j] != NULL) { node = get_tnode_index(logical_block[iblk].output_net_tnodes[i][j]); fprintf(fpout, "latch_%s re ", logical_block[iblk].name); } else { assert(0); } } i++; port = port->next; } assert(i == 1); i = 0; port = logical_block[iblk].model->inputs; while (port) { if (port->is_clock) { for (j = 0; j < port->size; j++) { if (logical_block[iblk].clock_net_tnode != NULL) { fprintf(fpout, "tnode_%d 0\n\n", get_tnode_index(logical_block[iblk].clock_net_tnode)); } else { assert(0); } } i++; } port = port->next; } assert(i == 1); } else { irr_graph = logical_block[iblk].pb->rr_graph; assert( irr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][0].pin_count_in_cluster].net_num != OPEN); fprintf(fpout, "tnode_%d ", get_tnode_index(irr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][0].pin_count_in_cluster].tnode)); node = get_tnode_index(irr_graph[logical_block[iblk].pb->pb_graph_node->output_pins[0][0].pin_count_in_cluster].tnode); fprintf(fpout, "latch_%s re ", logical_block[iblk].name); assert( irr_graph[logical_block[iblk].pb->pb_graph_node->clock_pins[0][0].pin_count_in_cluster].net_num != OPEN); fprintf(fpout, "tnode_%d 0\n\n", get_tnode_index(irr_graph[logical_block[iblk].pb->pb_graph_node->clock_pins[0][0].pin_count_in_cluster].tnode)); } assert(node != OPEN); fprintf(fpout, ".names latch_%s tnode_%d\n", logical_block[iblk].name, node); fprintf(fpout, "1 1\n\n"); } else if (strcmp(logical_block[iblk].model->name, "names") == 0) { fprintf(fpout, ".names "); node = OPEN; if (logical_block[iblk].pb == NULL) { i = 0; port = logical_block[iblk].model->inputs; while (port) { assert(!port->is_clock); for (j = 0; j < port->size; j++) { if (logical_block[iblk].input_net_tnodes[i][j] != NULL) { fprintf(fpout, "tnode_%d ", get_tnode_index(logical_block[iblk].input_net_tnodes[i][j])); } else { break; } } i++; port = port->next; } assert(i == 1); i = 0; port = logical_block[iblk].model->outputs; while (port) { assert(port->size == 1); fprintf(fpout, "lut_%s\n", logical_block[iblk].name); node = get_tnode_index(logical_block[iblk].output_net_tnodes[0][0]); assert(node != OPEN); i++; port = port->next; } assert(i == 1); } else { irr_graph = logical_block[iblk].pb->rr_graph; assert(logical_block[iblk].pb->pb_graph_node->num_input_ports == 1); for (i = 0; i < logical_block[iblk].pb->pb_graph_node->num_input_pins[0]; i++) { if(logical_block[iblk].input_nets[0][i] != OPEN) { for (j = 0; j < logical_block[iblk].pb->pb_graph_node->num_input_pins[0]; j++) { if (irr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][j].pin_count_in_cluster].net_num != OPEN) { if (irr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][j].pin_count_in_cluster].net_num == logical_block[iblk].input_nets[0][i]) { fprintf(fpout, "tnode_%d ", get_tnode_index(irr_graph[logical_block[iblk].pb->pb_graph_node->input_pins[0][j].pin_count_in_cluster].tnode)); break; } } } assert(j < logical_block[iblk].pb->pb_graph_node->num_input_pins[0]); } } assert( logical_block[iblk].pb->pb_graph_node->num_output_ports == 1); assert( logical_block[iblk].pb->pb_graph_node->num_output_pins[0] == 1); fprintf(fpout, "lut_%s\n", logical_block[iblk].name); node = get_tnode_index(irr_graph[logical_block[iblk].pb->pb_graph_node->output_pins[0][0].pin_count_in_cluster].tnode); } assert(node != OPEN); truth_table = logical_block[iblk].truth_table; while (truth_table) { fprintf(fpout, "%s\n", (char*) truth_table->data_vptr); truth_table = truth_table->next; } fprintf(fpout, "\n"); fprintf(fpout, ".names lut_%s tnode_%d\n", logical_block[iblk].name, node); fprintf(fpout, "1 1\n\n"); } else { /* This is a standard .subckt blif structure */ fprintf(fpout, ".subckt %s ", logical_block[iblk].model->name); if (logical_block[iblk].pb == NULL) { i = 0; port = logical_block[iblk].model->inputs; while (port) { if (!port->is_clock) { for (j = 0; j < port->size; j++) { if (logical_block[iblk].input_net_tnodes[i][j] != NULL) { if (port->size > 1) { fprintf(fpout, "\\\n%s[%d]=tnode_%d ", port->name, j, get_tnode_index(logical_block[iblk].input_net_tnodes[i][j])); } else { fprintf(fpout, "\\\n%s=tnode_%d ", port->name, get_tnode_index(logical_block[iblk].input_net_tnodes[i][j])); } } else { if (port->size > 1) { fprintf(fpout, "\\\n%s[%d]=unconn ", port->name, j); } else { fprintf(fpout, "\\\n%s=unconn ", port->name); } } } i++; } port = port->next; } i = 0; port = logical_block[iblk].model->outputs; while (port) { for (j = 0; j < port->size; j++) { if (logical_block[iblk].output_net_tnodes[i][j] != NULL) { if (port->size > 1) { fprintf(fpout, "\\\n%s[%d]=%s ", port->name, j, vpack_net[logical_block[iblk].output_nets[i][j]].name); } else { fprintf(fpout, "\\\n%s=%s ", port->name, vpack_net[logical_block[iblk].output_nets[i][j]].name); } } else { if (port->size > 1) { fprintf(fpout, "\\\n%s[%d]=unconn_%d_%s_%d ", port->name, j, iblk, port->name, j); } else { fprintf(fpout, "\\\n%s=unconn_%d_%s ", port->name, iblk, port->name); } } } i++; port = port->next; } i = 0; port = logical_block[iblk].model->inputs; while (port) { if (port->is_clock) { assert(port->size == 1); if (logical_block[iblk].clock_net_tnode != NULL) { fprintf(fpout, "\\\n%s=tnode_%d ", port->name, get_tnode_index(logical_block[iblk].clock_net_tnode)); } else { fprintf(fpout, "\\\n%s=unconn ", port->name); } i++; } port = port->next; } fprintf(fpout, "\n\n"); i = 0; port = logical_block[iblk].model->outputs; while (port) { for (j = 0; j < port->size; j++) { if (logical_block[iblk].output_net_tnodes[i][j] != NULL) { fprintf(fpout, ".names %s tnode_%d\n", vpack_net[logical_block[iblk].output_nets[i][j]].name, get_tnode_index(logical_block[iblk].output_net_tnodes[i][j])); fprintf(fpout, "1 1\n\n"); } } i++; port = port->next; } } else { irr_graph = logical_block[iblk].pb->rr_graph; pb_graph_node = logical_block[iblk].pb->pb_graph_node; for (i = 0; i < pb_graph_node->num_input_ports; i++) { for (j = 0; j < pb_graph_node->num_input_pins[i]; j++) { if (irr_graph[pb_graph_node->input_pins[i][j].pin_count_in_cluster].net_num != OPEN) { if (pb_graph_node->num_input_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=tnode_%d ", pb_graph_node->input_pins[i][j].port->name, j, get_tnode_index(irr_graph[pb_graph_node->input_pins[i][j].pin_count_in_cluster].tnode)); } else { fprintf(fpout, "\\\n%s=tnode_%d ", pb_graph_node->input_pins[i][j].port->name, get_tnode_index(irr_graph[pb_graph_node->input_pins[i][j].pin_count_in_cluster].tnode)); } } else { if (pb_graph_node->num_input_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=unconn ", pb_graph_node->input_pins[i][j].port->name, j); } else { fprintf(fpout, "\\\n%s=unconn ", pb_graph_node->input_pins[i][j].port->name); } } } } for (i = 0; i < pb_graph_node->num_output_ports; i++) { for (j = 0; j < pb_graph_node->num_output_pins[i]; j++) { if (irr_graph[pb_graph_node->output_pins[i][j].pin_count_in_cluster].net_num != OPEN) { if (pb_graph_node->num_output_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=%s ", pb_graph_node->output_pins[i][j].port->name, j, vpack_net[irr_graph[pb_graph_node->output_pins[i][j].pin_count_in_cluster].net_num].name); } else { char* port_name = pb_graph_node->output_pins[i][j].port->name; int pin_count = pb_graph_node->output_pins[i][j].pin_count_in_cluster; int node_index = irr_graph[pin_count].net_num; char* node_name = vpack_net[node_index].name; fprintf(fpout, "\\\n%s=%s ", port_name, node_name); } } else { if (pb_graph_node->num_output_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=unconn ", pb_graph_node->output_pins[i][j].port->name, j); } else { fprintf(fpout, "\\\n%s=unconn ", pb_graph_node->output_pins[i][j].port->name); } } } } for (i = 0; i < pb_graph_node->num_clock_ports; i++) { for (j = 0; j < pb_graph_node->num_clock_pins[i]; j++) { if (irr_graph[pb_graph_node->clock_pins[i][j].pin_count_in_cluster].net_num != OPEN) { if (pb_graph_node->num_clock_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=tnode_%d ", pb_graph_node->clock_pins[i][j].port->name, j, get_tnode_index(irr_graph[pb_graph_node->clock_pins[i][j].pin_count_in_cluster].tnode)); } else { fprintf(fpout, "\\\n%s=tnode_%d ", pb_graph_node->clock_pins[i][j].port->name, get_tnode_index(irr_graph[pb_graph_node->clock_pins[i][j].pin_count_in_cluster].tnode)); } } else { if (pb_graph_node->num_clock_pins[i] > 1) { fprintf(fpout, "\\\n%s[%d]=unconn ", pb_graph_node->clock_pins[i][j].port->name, j); } else { fprintf(fpout, "\\\n%s=unconn ", pb_graph_node->clock_pins[i][j].port->name); } } } } fprintf(fpout, "\n\n"); /* connect up output port names to output tnodes */ for (i = 0; i < pb_graph_node->num_output_ports; i++) { for (j = 0; j < pb_graph_node->num_output_pins[i]; j++) { if (irr_graph[pb_graph_node->output_pins[i][j].pin_count_in_cluster].net_num != OPEN) { fprintf(fpout, ".names %s tnode_%d\n", vpack_net[irr_graph[pb_graph_node->output_pins[i][j].pin_count_in_cluster].net_num].name, get_tnode_index(irr_graph[pb_graph_node->output_pins[i][j].pin_count_in_cluster].tnode)); fprintf(fpout, "1 1\n\n"); } } } } } }