#include #include #include #include #include "assert.h" #include "util.h" #include "vpr_types.h" #include "globals.h" #include "read_sdc.h" #include "read_blif.h" #include "path_delay.h" #include "path_delay2.h" #include "ReadOptions.h" #include "slre.h" /***************************** Summary **********************************/ /* Author: Michael Wainberg Looks for an SDC (Synopsys Design Constraints) file called .sdc (unless overridden with --sdc_file on the command-line, in which case it looks for that filename), and parses the timing constraints in that file. If it doesn't find a file with that name, it uses default timing constraints (which differ depending on whether the circuit has 0, 1, or multiple clocks). The primary routine, read_sdc, populates a container structure, g_sdc. One of the two key output data structures within is g_sdc->constrained_clocks, which associates each clock given a timing constraint with a name, fanout and whether it is a netlist or virtual (external) clock. From this point on, the only clocks we care about are the ones in this array. During timing analysis and data output, clocks are accessed using the indexing of this array. The other key data structure is the "constraint matrix" g_sdc->domain_constraint, which has a timing constraint for each pair (source and sink) of clock domains. These generally come from finding the smallest difference between the posedges of the two clocks over the LCM clock period ("edge counting" - see calculate_constraint()). Alternatively, entries in g_sdc->domain_constraint can come from a special-case, "override constraint" (so named because it overrides the default behaviour of edge counting). Override constraints can cut paths (set_clock_groups, set_false_path commands), create a multicycle (set_multicycle_path) or even override a constraint with a user- specified one (set_max_delay). These entries are stored temporarily in g_sdc->cc_constraints (cc = clock to clock), which is freed once the timing_constraints echo file is created during process_constraints(). Flip-flop-level override constraints also exist and are stored in g_sdc->cf_constraints, g_sdc->fc_constraints and g_sdc->ff_constraints (depending on whether the source, sink or neither of the two is a clock domain).Unlike g_sdc->cc_constraints, they are placed on the timing graph during timing analysis instead of going into g_sdc->domain_constraint, and are not freed until the end of VPR's execution. I/O constraints from set_input_delay and set_output_delay are stored in constrained_ inputs and g_sdc->constrained_outputs. These associate each I/O in the netlist given a constraint with the clock (often virtual, but could be in the netlist) it was constrained on, and the delay through the I/O in that constraint. The remaining data structures are temporary and local to this file: netlist_clocks, netlist_inputs and netlist_outputs, which are used to match names of clocks and I/Os in the SDC file to those in the netlist; sdc_clocks, which stores info on clock periods and offsets from create_clock commands and is the raw info used in edge counting; and exclusive_groups, used when parsing set_clock_groups commands into g_sdc->cc_constraints. */ /*********************** Externally-accessible variables **************************/ t_timing_constraints * g_sdc = NULL; /****************** Types local to this module **************************/ typedef struct s_sdc_clock { char * name; float period; float rising_edge; float falling_edge; } t_sdc_clock; /* Stores the name, period and offset of each constrained clock. */ typedef struct s_sdc_exclusive_group { char ** clock_names; int num_clock_names; } t_sdc_exclusive_group; /* Used to temporarily separate clock names into exclusive groups when parsing the command set_clock_groups -exclusive. */ /****************** Variables local to this module **************************/ static FILE *sdc; t_sdc_clock * sdc_clocks = NULL; /* List of clock periods and offsets from create_clock commands */ int num_netlist_clocks = 0; /* number of clocks in netlist */ char ** netlist_clocks; /* [0..num_netlist_clocks - 1] array of names of clocks in netlist */ int num_netlist_ios = 0; /* number of clocks in netlist */ char ** netlist_ios; /* [0..num_netlist_clocks - 1] array of names of ios in netlist */ /***************** Subroutines local to this module *************************/ static void alloc_and_load_netlist_clocks_and_ios(void); static void use_default_timing_constraints(void); static void count_netlist_clocks_as_constrained_clocks(void); static boolean get_sdc_tok(char * buf); static boolean is_number(char * ptr); static int find_constrained_clock(char * ptr); static float calculate_constraint(t_sdc_clock source_domain, t_sdc_clock sink_domain); static void add_override_constraint(char ** from_list, int num_from, char ** to_list, int num_to, float constraint, int num_multicycles, boolean domain_level_from, boolean domain_level_to, boolean make_copies); static int find_cc_constraint(char * source_clock_domain, char * sink_clock_domain); static boolean regex_match (char *string, char *pattern); static void count_netlist_ios_as_constrained_ios(char * clock_name, float io_delay); static void free_io_constraint(t_io *& io_array, int num_ios); static void free_clock_constraint(t_clock *& clock_array, int num_clocks); /********************* Subroutine definitions *******************************/ void read_sdc(t_timing_inf timing_inf) { char buf[BUFSIZE]; int source_clock_domain, sink_clock_domain, iinput, ioutput, icc, isource, isink; boolean found; /* Make sure we haven't called this subroutine before. */ assert(!g_sdc); /* Allocate container structure for SDC constraints. */ g_sdc = (t_timing_constraints *) my_calloc(1, sizeof(t_timing_constraints)); /* Reset file line number. */ file_line_number = 0; /* If no SDC file is included or specified, or timing analysis is off, use default behaviour of cutting paths between domains and optimizing each clock separately */ if (!timing_inf.timing_analysis_enabled) { vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Timing analysis off; using default timing constraints.\n"); use_default_timing_constraints(); return; } if ((sdc = fopen(timing_inf.SDCFile, "r")) == NULL) { vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "SDC file '%s' blank or not found.\n", timing_inf.SDCFile); use_default_timing_constraints(); return; } /* Now we have an SDC file. */ /* Count how many clocks and I/Os are in the netlist. Store the names of each clock and each I/O in netlist_clocks and netlist_ios. The only purpose of these two lists is to compare clock names in the SDC file against them. As a result, they will be freed after the SDC file is parsed. */ alloc_and_load_netlist_clocks_and_ios(); /* Parse the file line-by-line. */ found = FALSE; while (my_fgets(buf, BUFSIZE, sdc) != NULL) { if (get_sdc_tok(buf)) { found = TRUE; } } if (!found) { /* blank file or only comments found */ vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "SDC file '%s' blank or not found.\n", timing_inf.SDCFile); use_default_timing_constraints(); free(netlist_clocks); free(netlist_ios); return; } fclose(sdc); /* Make sure that all virtual clocks referenced in g_sdc->constrained_inputs and g_sdc->constrained_outputs have been constrained. */ for (iinput = 0; iinput < g_sdc->num_constrained_inputs; iinput++) { if ((find_constrained_clock(g_sdc->constrained_inputs[iinput].clock_name)) == -1) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Input %s is associated with an unconstrained clock %s.\n", g_sdc->constrained_inputs[iinput].file_line_number, g_sdc->constrained_inputs[iinput].name, g_sdc->constrained_inputs[iinput].clock_name); exit(1); } } for (ioutput = 0; ioutput < g_sdc->num_constrained_outputs; ioutput++) { if ((find_constrained_clock(g_sdc->constrained_outputs[ioutput].clock_name)) == -1) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Output %s is associated with an unconstrained clock %s.\n", g_sdc->constrained_inputs[iinput].file_line_number, g_sdc->constrained_outputs[ioutput].name, g_sdc->constrained_outputs[ioutput].clock_name); exit(1); } } /* Make sure that all clocks referenced in g_sdc->cc_constraints have been constrained. */ for (icc = 0; icc < g_sdc->num_cc_constraints; icc++) { for (isource = 0; isource < g_sdc->cc_constraints[icc].num_source; isource++) { if ((find_constrained_clock(g_sdc->cc_constraints[icc].source_list[isource])) == -1) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Token %s is not a constrained clock.\n", g_sdc->cc_constraints[icc].file_line_number, g_sdc->cc_constraints[icc].source_list[isource]); exit(1); } } for (isink = 0; isink < g_sdc->cc_constraints[icc].num_sink; isink++) { if ((find_constrained_clock(g_sdc->cc_constraints[icc].sink_list[isink])) == -1) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Token %s is not a constrained clock.\n", g_sdc->cc_constraints[icc].file_line_number, g_sdc->cc_constraints[icc].sink_list[isink]); exit(1); } } } /* Allocate matrix of timing constraints [0..g_sdc->num_constrained_clocks-1][0..g_sdc->num_constrained_clocks-1] and initialize to 0 */ g_sdc->domain_constraint = (float **) alloc_matrix(0, g_sdc->num_constrained_clocks-1, 0, g_sdc->num_constrained_clocks-1, sizeof(float)); /* Based on the information from sdc_clocks, calculate constraints for all paths except ones with an override constraint. */ 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 ((icc = find_cc_constraint(g_sdc->constrained_clocks[source_clock_domain].name, g_sdc->constrained_clocks[sink_clock_domain].name)) != -1) { if (g_sdc->cc_constraints[icc].num_multicycles == 0) { /* There's a special constraint from set_false_path, set_clock_groups -exclusive or set_max_delay which overrides the default constraint. */ g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = g_sdc->cc_constraints[icc].constraint; } else { /* There's a special constraint from set_multicycle_path which overrides the default constraint. This constraint = default constraint (obtained via edge counting) + (num_multicycles - 1) * period of sink clock domain. */ g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = calculate_constraint(sdc_clocks[source_clock_domain], sdc_clocks[sink_clock_domain]) + (g_sdc->cc_constraints[icc].num_multicycles - 1) * sdc_clocks[sink_clock_domain].period; } } else { /* There's no special override constraint. */ /* Calculate the constraint between clock domains by finding the smallest positive difference between a posedge in the source domain and one in the sink domain. */ g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = calculate_constraint(sdc_clocks[source_clock_domain], sdc_clocks[sink_clock_domain]); } } } vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "SDC file '%s' parsed successfully.\n", timing_inf.SDCFile ); vpr_printf(TIO_MESSAGE_INFO, "%d clocks (including virtual clocks), %d inputs and %d outputs were constrained.\n", g_sdc->num_constrained_clocks, g_sdc->num_constrained_inputs, g_sdc->num_constrained_outputs); vpr_printf(TIO_MESSAGE_INFO, "\n"); /* Since all the information we need is stored in g_sdc->domain_constraint, g_sdc->constrained_clocks, and constrained_ios, free other data structures used in this routine */ free(sdc_clocks); free(netlist_clocks); free(netlist_ios); return; } static void use_default_timing_constraints(void) { int source_clock_domain, sink_clock_domain; /* Find all netlist clocks and add them as constrained clocks. */ count_netlist_clocks_as_constrained_clocks(); /* We'll use separate defaults for multi-clock and single-clock/combinational circuits. */ if (g_sdc->num_constrained_clocks <= 1) { /* Create one constrained clock with period 0... */ g_sdc->domain_constraint = (float **) alloc_matrix(0, 0, 0, 0, sizeof(float)); g_sdc->domain_constraint[0][0] = 0.; if (g_sdc->num_constrained_clocks == 0) { /* We need to create a virtual clock to constrain I/Os on. */ g_sdc->num_constrained_clocks = 1; g_sdc->constrained_clocks = (t_clock *) my_malloc(sizeof(t_clock)); g_sdc->constrained_clocks[0].name = my_strdup("virtual_io_clock"); g_sdc->constrained_clocks[0].is_netlist_clock = FALSE; vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Defaulting to: constrain all %d inputs and %d outputs on a virtual external clock.\n", g_sdc->num_constrained_inputs, g_sdc->num_constrained_outputs); vpr_printf(TIO_MESSAGE_INFO, "Optimize this virtual clock to run as fast as possible.\n"); } else { vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Defaulting to: constrain all %d inputs and %d outputs on the netlist clock.\n", g_sdc->num_constrained_inputs, g_sdc->num_constrained_outputs); vpr_printf(TIO_MESSAGE_INFO, "Optimize this clock to run as fast as possible.\n"); } /* Constrain all I/Os on the single constrained clock (whether real or virtual), with I/O delay 0. */ count_netlist_ios_as_constrained_ios(g_sdc->constrained_clocks[0].name, 0.); } else { /* Multiclock circuit */ /* Constrain all I/Os on a separate virtual clock. Cut paths between all netlist clocks, but analyse all paths between the virtual I/O clock and netlist clocks and optimize all clocks to go as fast as possible. */ g_sdc->constrained_clocks = (t_clock *) my_realloc (g_sdc->constrained_clocks, ++g_sdc->num_constrained_clocks * sizeof(t_clock)); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].name = my_strdup("virtual_io_clock"); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].is_netlist_clock = FALSE; count_netlist_ios_as_constrained_ios(g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].name, 0.); /* Allocate matrix of timing constraints [0..g_sdc->num_constrained_clocks-1][0..g_sdc->num_constrained_clocks-1] */ g_sdc->domain_constraint = (float **) alloc_matrix(0, g_sdc->num_constrained_clocks-1, 0, g_sdc->num_constrained_clocks-1, sizeof(float)); 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 (source_clock_domain == sink_clock_domain || source_clock_domain == g_sdc->num_constrained_clocks - 1 || sink_clock_domain == g_sdc->num_constrained_clocks - 1) { g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = 0.; } else { g_sdc->domain_constraint[source_clock_domain][sink_clock_domain] = DO_NOT_ANALYSE; } } } vpr_printf(TIO_MESSAGE_INFO, "\n"); vpr_printf(TIO_MESSAGE_INFO, "Defaulting to: constrain all %d inputs and %d outputs on a virtual external clock;\n", g_sdc->num_constrained_inputs, g_sdc->num_constrained_outputs); vpr_printf(TIO_MESSAGE_INFO, "\tcut paths between netlist clock domains; and\n"); vpr_printf(TIO_MESSAGE_INFO, "\toptimize all clocks to run as fast as possible.\n"); } } static void alloc_and_load_netlist_clocks_and_ios(void) { /* Count how many clocks and I/Os are in the netlist. Store the names of each clock and each I/O in netlist_clocks and netlist_ios. */ int iblock, i, clock_net; char * name; boolean found; for (iblock = 0; iblock < num_logical_blocks; iblock++) { if (logical_block[iblock].clock_net != OPEN) { clock_net = logical_block[iblock].clock_net; assert(clock_net != OPEN); name = logical_block[clock_net].name; /* Now that we've found a clock, let's see if we've counted it already */ found = FALSE; for (i = 0; !found && i < num_netlist_clocks; i++) { if (strcmp(netlist_clocks[i], name) == 0) { found = TRUE; } } if (!found) { /* If we get here, the clock is new and so we dynamically grow the array netlist_clocks by one. */ netlist_clocks = (char **) my_realloc (netlist_clocks, ++num_netlist_clocks * sizeof(char *)); netlist_clocks[num_netlist_clocks - 1] = name; } } else if (logical_block[iblock].type == VPACK_INPAD || logical_block[iblock].type == VPACK_OUTPAD) { name = logical_block[iblock].name; /* Now that we've found an I/O, let's see if we've counted it already */ found = FALSE; for (i = 0; !found && i < num_netlist_ios; i++) { if (strcmp(netlist_ios[i], name) == 0) { found = TRUE; } } if (!found) { /* If we get here, the I/O is new and so we dynamically grow the array netlist_ios by one. */ netlist_ios = (char **) my_realloc (netlist_ios, ++num_netlist_ios * sizeof(char *)); netlist_ios[num_netlist_ios - 1] = logical_block[iblock].type == VPACK_OUTPAD ? name + 4 : name; /* the + 4 removes the prefix "out:" automatically prepended to outputs */ } } } } static void count_netlist_clocks_as_constrained_clocks(void) { /* Counts how many clocks are in the netlist, and adds them to the array g_sdc->constrained_clocks. */ int iblock, i, clock_net; char * name; boolean found; g_sdc->num_constrained_clocks = 0; for (iblock = 0; iblock < num_logical_blocks; iblock++) { if (logical_block[iblock].clock_net != OPEN) { clock_net = logical_block[iblock].clock_net; assert(clock_net != OPEN); name = logical_block[clock_net].name; /* Now that we've found a clock, let's see if we've counted it already */ found = FALSE; for (i = 0; !found && i < g_sdc->num_constrained_clocks; i++) { if (strcmp(g_sdc->constrained_clocks[i].name, name) == 0) { found = TRUE; } } if (!found) { /* If we get here, the clock is new and so we dynamically grow the array g_sdc->constrained_clocks by one. */ g_sdc->constrained_clocks = (t_clock *) my_realloc (g_sdc->constrained_clocks, ++g_sdc->num_constrained_clocks * sizeof(t_clock)); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].name = my_strdup(name); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].is_netlist_clock = TRUE; /* Fanout will be filled out once the timing graph has been constructed. */ } } } } static void count_netlist_ios_as_constrained_ios(char * clock_name, float io_delay) { /* Count how many I/Os are in the netlist, adds them to the arrays g_sdc->constrained_inputs/ g_sdc->constrained_outputs with an I/O delay of 0 and constrains them to clock clock_name. */ int iblock, iinput, ioutput; char * name; boolean found; for (iblock = 0; iblock < num_logical_blocks; iblock++) { if (logical_block[iblock].type == VPACK_INPAD) { name = logical_block[iblock].name; /* Now that we've found an I/O, let's see if we've counted it already */ found = FALSE; for (iinput = 0; !found && iinput < g_sdc->num_constrained_inputs; iinput++) { if (strcmp(g_sdc->constrained_inputs[iinput].name, name) == 0) { found = TRUE; } } if (!found) { /* If we get here, the input is new and so we add it to g_sdc->constrained_inputs. */ g_sdc->constrained_inputs = (t_io *) my_realloc (g_sdc->constrained_inputs, ++g_sdc->num_constrained_inputs * sizeof(t_io)); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].name = my_strdup(name); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].clock_name = my_strdup(clock_name); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].delay = 0.; } } else if (logical_block[iblock].type == VPACK_OUTPAD) { name = logical_block[iblock].name; /* Now that we've found an I/O, let's see if we've counted it already */ found = FALSE; for (ioutput = 0; !found && ioutput < g_sdc->num_constrained_outputs; ioutput++) { if (strcmp(g_sdc->constrained_outputs[ioutput].name, name) == 0) { found = TRUE; } } if (!found) { /* If we get here, the output is new and so we add it to g_sdc->constrained_outputs. */ g_sdc->constrained_outputs = (t_io *) my_realloc (g_sdc->constrained_outputs, ++g_sdc->num_constrained_outputs * sizeof(t_io)); g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].name = my_strdup(name + 4); /* the + 4 removes the prefix "out:" automatically prepended to outputs */ g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].clock_name = my_strdup(clock_name); g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].delay = 0.; } } } } static boolean get_sdc_tok(char * buf) { /* Figures out which tokens are on this line and takes the appropriate actions. Returns true if anything non-commented is found on this line. */ #define SDC_TOKENS " \t\n{}[]" /* We can ignore braces. */ char * ptr, ** from_list = NULL, ** to_list = NULL, * clock_name; float clock_period, rising_edge, falling_edge, max_delay; int iclock, iio, num_exclusive_groups = 0, num_from = 0, num_to = 0, num_multicycles, i, j; t_sdc_exclusive_group * exclusive_groups = NULL; boolean found, domain_level_from = FALSE, domain_level_to = FALSE; /* my_strtok splits the string into tokens - little character arrays separated by the SDC_TOKENS defined above. Throughout this code, ptr refers to the tokens we fetch, one at a time. The token changes at each call of my_strtok. We call my_strtok with NULL as the first argument every time AFTER the first, since this picks up tokenizing where we left off. We always wrap each call to my_strtok with a check that ptr is non-null to avoid an exception from passing NULL into strcmp. */ if ((ptr = my_strtok(buf, SDC_TOKENS, sdc, buf)) == NULL) {/* blank line */ return FALSE; } if (strcmp(ptr, "create_clock") == 0) { /* Syntax: create_clock -period [-waveform {rising_edge falling_edge}] or create_clock -period [-waveform {rising_edge falling_edge}] -name */ /* make sure clock has -period specified */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-period") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Create_clock must be directly followed by '-period'.\n", file_line_number); exit(1); } /* Check if the token following -period is actually a number. */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] A number must follow '-period'.\n", file_line_number); exit(1); } clock_period = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Clock(s) not specified.\n", file_line_number); exit(1); } if (strcmp(ptr, "-waveform") == 0) { /* Get the first float, which is the rising edge, and the second, which is the falling edge. */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] First token following '-waveform' should be rising edge, but is not a number.\n", file_line_number); } rising_edge = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Second token following '-waveform' should be falling edge, but is not a number.\n", file_line_number); exit(1); } falling_edge = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Clock(s) not specified.\n", file_line_number); exit(1); } /* We need this extra call to my_strtok to advance the ptr to the right spot. */ } else { /* The clock's rising edge is by default at 0, and the falling edge is at the half-period. */ rising_edge = 0.; falling_edge = clock_period / 2.0; } if (strcmp(ptr, "-name") == 0) { /* For external virtual clocks only (used with I/O constraints). Only one virtual clock can be specified per line, so make sure there's only one token left on this line. */ /* Get the virtual clock name */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Virtual clock name not specified.\n", file_line_number); exit(1); } /* We need this extra call to my_strtok to advance the ptr to the right spot. */ /* We've found a new clock! */ /* Store the clock's name, period and edges in the local array sdc_clocks. */ sdc_clocks = (t_sdc_clock *) my_realloc(sdc_clocks, ++g_sdc->num_constrained_clocks * sizeof(t_sdc_clock)); sdc_clocks[g_sdc->num_constrained_clocks - 1].name = ptr; sdc_clocks[g_sdc->num_constrained_clocks - 1].period = clock_period; sdc_clocks[g_sdc->num_constrained_clocks - 1].rising_edge = rising_edge; sdc_clocks[g_sdc->num_constrained_clocks - 1].falling_edge = falling_edge; /* Also store the clock's name, and the fact that it is not a netlist clock, in g_sdc->constrained_clocks. */ g_sdc->constrained_clocks = (t_clock *) my_realloc (g_sdc->constrained_clocks, g_sdc->num_constrained_clocks * sizeof(t_clock)); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].name = my_strdup(ptr); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].is_netlist_clock = FALSE; /* Fanout will be filled out once the timing graph has been constructed. */ /* The next token should be NULL. If so, return; if not, print an error message and exit. */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] More than one virtual clock name is specified after '-name'.\n", file_line_number); exit(1); } } else { /* Parse through to the end of the line. All that should be left on this line are one or more * regular expressions denoting netlist clocks to be associated with this clock period. An array sdc_clocks will * store the period and offset of each clock at the same index which that clock has in netlist_clocks. Later, * after everything has been parsed, we take the information from this array to calculate the actual timing constraints * which these periods and offsets imply, and put them in the matrix g_sdc->domain_constraint. */ do { /* See if the regular expression stored in ptr is legal and matches at least one clock net. If it is not legal, it will fail during regex_match. We check for a match using boolean found. */ found = FALSE; for (iclock = 0; iclock < num_netlist_clocks; iclock++) { if (regex_match(netlist_clocks[iclock], ptr)) { /* We've found a new clock! (Note that we can't store ptr as the clock's name since it could be a regex, unlike the virtual clock case).*/ found = TRUE; /* Store the clock's name, period and edges in the local array sdc_clocks. */ sdc_clocks = (t_sdc_clock *) my_realloc(sdc_clocks, ++g_sdc->num_constrained_clocks * sizeof(t_sdc_clock)); sdc_clocks[g_sdc->num_constrained_clocks - 1].name = netlist_clocks[iclock]; sdc_clocks[g_sdc->num_constrained_clocks - 1].period = clock_period; sdc_clocks[g_sdc->num_constrained_clocks - 1].rising_edge = rising_edge; sdc_clocks[g_sdc->num_constrained_clocks - 1].falling_edge = falling_edge; /* Also store the clock's name, and the fact that it is a netlist clock, in g_sdc->constrained_clocks. */ g_sdc->constrained_clocks = (t_clock *) my_realloc (g_sdc->constrained_clocks, g_sdc->num_constrained_clocks * sizeof(t_clock)); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].name = my_strdup(netlist_clocks[iclock]); g_sdc->constrained_clocks[g_sdc->num_constrained_clocks - 1].is_netlist_clock = TRUE; /* Fanout will be filled out once the timing graph has been constructed. */ } } if (!found) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Clock name or regular expression does not correspond to any nets.\n", file_line_number); vpr_printf(TIO_MESSAGE_ERROR, "If you'd like to create a virtual clock, use the '-name' keyword.\n"); exit(1); } } while ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL); /* Advance to the next token (or the end of the line). */ } /* Warn if the clock has non-50% duty cycle. */ if (fabs(rising_edge - falling_edge) - clock_period/2.0 > EPSILON) { vpr_printf(TIO_MESSAGE_WARNING, "Clock %s does not have 50%% duty cycle.\n", sdc_clocks[g_sdc->num_constrained_clocks - 1].name); } return TRUE; } else if (strcmp(ptr, "set_clock_groups") == 0) { /* Syntax: set_clock_groups -exclusive -group {} -group {} [-group {} ...] */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-exclusive") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_clock_groups must be directly followed by '-exclusive'.\n", file_line_number); exit(1); } if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-group") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_clock_groups '-exclusive' must be followed by lists of clock names or regular expressions each starting with the '-group' command.\n", file_line_number); exit(1); } /* Parse through to the end of the line. All that should be left on this line are a bunch of -group commands, followed by groups of regexes. We need to ensure that paths are cut between every clock matching a regex in one group and every clock matching a regex in any other group. */ do { ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf); /* Create a new entry in exclusive groups */ exclusive_groups = (t_sdc_exclusive_group *) my_realloc( exclusive_groups, ++num_exclusive_groups * sizeof(t_sdc_exclusive_group)); exclusive_groups[num_exclusive_groups - 1].clock_names = NULL; exclusive_groups[num_exclusive_groups - 1].num_clock_names = 0; do { /* Check the regex ptr against each netlist clock and add it to the clock_names list if it matches. */ found = FALSE; for (iclock = 0; iclock < num_netlist_clocks; iclock++) { if (regex_match(netlist_clocks[iclock], ptr)) { found = TRUE; exclusive_groups[num_exclusive_groups - 1].clock_names = (char **) my_realloc( exclusive_groups[num_exclusive_groups - 1].clock_names, ++exclusive_groups[num_exclusive_groups - 1].num_clock_names * sizeof(char *)); exclusive_groups[num_exclusive_groups - 1].clock_names [exclusive_groups[num_exclusive_groups - 1].num_clock_names - 1] = my_strdup(netlist_clocks[iclock]); } } if (!found) { /* If no clocks matched, assume ptr is the name of a virtual clock and add it to the list. (If it's not a virtual clock, we'll catch it later when we check all override constraints.) */ exclusive_groups[num_exclusive_groups - 1].clock_names = (char **) my_realloc( exclusive_groups[num_exclusive_groups - 1].clock_names, ++exclusive_groups[num_exclusive_groups - 1].num_clock_names * sizeof(char *)); exclusive_groups[num_exclusive_groups - 1].clock_names [exclusive_groups[num_exclusive_groups - 1].num_clock_names - 1] = my_strdup(ptr); } } while ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL && strcmp(ptr, "-group") != 0); } while (ptr); if (num_exclusive_groups < 2) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] At least two '-group' commands required.", file_line_number); exit(1); } /* Finally, create two DO_NOT_ANALYSE override constraints for each pair of entries to cut paths bidirectionally between pairs of clock lists in different groups. Set make_copies to TRUE because we have to use the lists of names in multiple override constraints, and it's impossible to free them from multiple places at the end without a whole lot of trouble. */ for (i = 0; i < num_exclusive_groups; i++) { for (j = 0; j < num_exclusive_groups; j++) { if (i != j) { add_override_constraint(exclusive_groups[i].clock_names, exclusive_groups[i].num_clock_names, exclusive_groups[j].clock_names, exclusive_groups[j].num_clock_names, DO_NOT_ANALYSE, 0, TRUE, TRUE, TRUE); } } } /* Now that we've copied all the clock name lists (2 * num_exlusive_groups - 1) times, free the original lists. */ for (i = 0; i < num_exclusive_groups; i++) { for (j = 0; j < exclusive_groups[i].num_clock_names; j++) { free(exclusive_groups[i].clock_names[j]); } free(exclusive_groups[i].clock_names); } free (exclusive_groups); return TRUE; } else if (strcmp(ptr, "set_false_path") == 0) { /* Syntax: set_false_path -from -to */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-from") != 0 || (ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_false_path must be directly followed by '-from '.\n", file_line_number); exit(1); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_from = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_false_path must be directly followed by '-from '.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to from_list until we hit the -to command. */ from_list = (char **) my_realloc(from_list, ++num_from * sizeof(char *)); from_list[num_from - 1] = my_strdup(ptr); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { /* We hit the end of the line before finding a -to. */ vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_false_path requires '-to ' after '-from '.\n", file_line_number); exit(1); } } while (strcmp(ptr, "-to") != 0); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_false_path requires '-to ' after '-from '.\n", file_line_number); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_to = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_false_path must be directly followed by '-from '.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to to_list until we hit the end of the line. */ to_list = (char **) my_realloc(to_list, ++num_to * sizeof(char *)); to_list[num_to - 1] = my_strdup(ptr); } while ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL); /* Create a constraint between each element in from_list and each element in to_list with value DO_NOT_ANALYSE. Set make_copies to false since, as we only need to use from_list and to_list once, we can just have the override constraint entry point to those lists. */ add_override_constraint(from_list, num_from, to_list, num_to, DO_NOT_ANALYSE, 0, domain_level_from, domain_level_to, FALSE); /* Finally, set from_list and to_list to NULL since they're both being pointed to by the override constraint entry we just created. */ from_list = NULL, to_list = NULL; return TRUE; } else if (strcmp(ptr, "set_max_delay") == 0) { /* Syntax: set_max_delay -from -to */ /* Basically the same as set_false_path above, except we get a specific delay value for the constraint. */ /* check if the token following set_max_delay is actually a number*/ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Token following set_max_delay should be a delay value, but is not a number.\n", file_line_number); exit(1); } max_delay = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-from") != 0 || (ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_max_delay requires '-from ' after max_delay.\n", file_line_number); exit(1); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_from = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_max_delay requires '-from ' after max_delay.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to from_list until we hit the -to command. */ from_list = (char **) my_realloc(from_list, ++num_from * sizeof(char *)); from_list[num_from - 1] = my_strdup(ptr); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { /* We hit the end of the line before finding a -to. */ vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_max_delay requires '-to ' after '-from '.\n", file_line_number); exit(1); } } while (strcmp(ptr, "-to") != 0); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_max_delay requires '-to ' after '-from '.\n", file_line_number); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_to = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_max_delay requires '-to ' after '-from '.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to to_list until we hit the end of the line. */ to_list = (char **) my_realloc(to_list, ++num_to * sizeof(char *)); to_list[num_to - 1] = my_strdup(ptr); } while ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL); /* Create a constraint between each element in from_list and each element in to_list with value max_delay. */ add_override_constraint(from_list, num_from, to_list, num_to, max_delay, 0, domain_level_from, domain_level_to, FALSE); /* Finally, set from_list and to_list to NULL since they're both being pointed to by the override constraint entry we just created. */ from_list = NULL, to_list = NULL; return TRUE; } else if (strcmp(ptr, "set_multicycle_path") == 0) { /* Syntax: set_multicycle_path -setup -from -to */ /* Basically the same as set_false_path and set_max_delay above, except we have to calculate the default value of the constraint (obtained via edge counting) first, and then set a constraint equal to default constraint + (num_multicycles - 1) * period of sink clock domain. */ /* check if the token following set_max_delay is actually a number*/ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-setup") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path must be directly followed by '-setup'.\n", file_line_number); exit(1); } if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-from") != 0 || (ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path requires '-from ' after '-setup'.\n", file_line_number); exit(1); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_from = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path '-setup' must be followed by '-from '.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to from_list until we hit the -to command. */ from_list = (char **) my_realloc(from_list, ++num_from * sizeof(char *)); from_list[num_from - 1] = my_strdup(ptr); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { /* We hit the end of the line before finding a -to. */ vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path requires '-to ' after '-from '.\n", file_line_number); exit(1); } } while (strcmp(ptr, "-to") != 0); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path requires '-to ' after '-from '.\n", file_line_number); } if (strcmp(ptr, "get_clocks") == 0) { domain_level_to = TRUE; if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path requires '-to ' after '-from '.\n", file_line_number); exit(1); } } do { /* Keep adding clock names to to_list until we hit a number (i.e. num_multicycles). */ to_list = (char **) my_realloc(to_list, ++num_to * sizeof(char *)); to_list[num_to - 1] = my_strdup(ptr); } while (((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) != NULL) && !is_number(ptr)); if (!ptr) { /* We hit the end of the line before finding a number. */ vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_multicycle_path requires num_multicycles after '-to '.\n", file_line_number); exit(1); } num_multicycles = (int) strtod(ptr, NULL); /* Create an override constraint between from and to. Unlike the previous two commands, set_multicycle_path requires information about the periods and offsets of the clock domains which from and to, which we have to fill in at the end. */ add_override_constraint(from_list, num_from, to_list, num_to, HUGE_NEGATIVE_FLOAT /* irrelevant - never used */, num_multicycles, domain_level_from, domain_level_to, FALSE); /* Finally, set from_list and to_list to NULL since they're both being pointed to by the override constraint entry we just created. */ from_list = NULL, to_list = NULL; return TRUE; } else if (strcmp(ptr, "set_input_delay") == 0) { /* Syntax: set_input_delay -clock -max [get_ports {}] */ /* We want to assign virtual_clock to all input ports in port_list, and set the input delay (from the external device to the FPGA) to max_delay. */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-clock") != 0 || (ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_input_delay must be directly followed by '-clock '.\n", file_line_number); exit(1); } if (num_netlist_clocks == 1 && strcmp(ptr, "*") == 0) { /* Allow the user to wildcard the clock name if there's only one clock (not standard SDC but very convenient). */ clock_name = netlist_clocks[0]; } else { /* We have no way of error-checking whether this is an actual virtual clock until we finish parsing. */ clock_name = ptr; } if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-max") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_input_delay '-clock ' must be directly followed by '-max '.\n", file_line_number); exit(1); } /* check if the token following -max is actually a number*/ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Token following '-max' should be a delay value, but is not a number.\n", file_line_number); exit(1); } max_delay = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "get_ports") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_input_delay requires a [get_ports {...}] command following '-max '.\n", file_line_number); exit(1); } /* Parse through to the end of the line. Add each regular expression match we find to the list of constrained inputs and give each entry the virtual clock name and max_delay we've just parsed. We have no way of error-checking whether these tokens correspond to actual input ports until later. */ for (;;) { if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { /* end of line */ return TRUE; } found = FALSE; for (iio = 0; iio < num_netlist_ios; iio++) { /* See if the regular expression stored in ptr is legal and matches at least one input port. If it is not legal, it will fail during regex_match. We check for a match using boolean found. */ if (regex_match(netlist_ios[iio], ptr)) { /* We've found a new input! */ g_sdc->num_constrained_inputs++; found = TRUE; /* Fill in input information in the permanent array g_sdc->constrained_inputs. */ g_sdc->constrained_inputs = (t_io *) my_realloc (g_sdc->constrained_inputs, g_sdc->num_constrained_inputs * sizeof(t_io)); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].name = my_strdup(netlist_ios[iio]); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].clock_name = my_strdup(clock_name); g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].delay = max_delay; g_sdc->constrained_inputs[g_sdc->num_constrained_inputs - 1].file_line_number = file_line_number; /* global var */ } } if (!found) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Output name or regular expression \"%s\" does not correspond to any nets.\n", file_line_number, ptr); exit(1); } } } else if (strcmp(ptr, "set_output_delay") == 0) { /* Syntax: set_output_delay -clock -max [get_ports {}] */ /* We want to assign virtual_clock to all output ports in port_list, and set the output delay (from the external device to the FPGA) to max_delay. */ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-clock") != 0 || (ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_output_delay must be directly followed by '-clock '.\n", file_line_number); exit(1); } if (num_netlist_clocks == 1 && strcmp(ptr, "*") == 0) { /* Allow the user to wildcard the clock name if there's only one clock (not standard SDC but very convenient). */ clock_name = netlist_clocks[0]; } else { /* We have no way of error-checking whether this is an actual virtual clock until we finish parsing. */ clock_name = ptr; } if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "-max") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_output_delay -clock must be directly followed by '-max '.\n", file_line_number); exit(1); } /* check if the token following -max is actually a number*/ if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || !is_number(ptr)) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Token following '-max' should be a delay value, but is not a number.\n", file_line_number); exit(1); } max_delay = (float) strtod(ptr, NULL); if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL || strcmp(ptr, "get_ports") != 0) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] set_output_delay requires a [get_ports {...}] command following '-max '.\n", file_line_number); exit(1); } /* Parse through to the end of the line. Add each regular expression match we find to the list of constrained outputs and give each entry the virtual clock name and max_delay we've just parsed. We have no way of error-checking whether these tokens correspond to actual output ports until later. */ for (;;) { if ((ptr = my_strtok(NULL, SDC_TOKENS, sdc, buf)) == NULL) { /* end of line */ return TRUE; } found = FALSE; for (iio = 0; iio < num_netlist_ios; iio++) { /* See if the regular expression stored in ptr is legal and matches at least one output port. If it is not legal, it will fail during regex_match. We check for a match using boolean found. */ if (regex_match(netlist_ios[iio], ptr)) { /* We've found a new output! */ g_sdc->num_constrained_outputs++; found = TRUE; /* Fill in output information in the permanent array g_sdc->constrained_outputs. */ g_sdc->constrained_outputs = (t_io *) my_realloc (g_sdc->constrained_outputs, g_sdc->num_constrained_outputs * sizeof(t_io)); g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].name = my_strdup(netlist_ios[iio]); g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].clock_name = my_strdup(clock_name); g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].delay = max_delay; g_sdc->constrained_outputs[g_sdc->num_constrained_outputs - 1].file_line_number = file_line_number; /* global var */ } } if (!found) { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Output name or regular expression \"%s\" does not correspond to any nets.\n", file_line_number, ptr); exit(1); } } } else { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Incorrect or unsupported syntax near start of line.\n", file_line_number); exit(1); } } static boolean is_number(char * ptr) { /* Checks if the character array ptr represents a valid floating-point number. * * To return TRUE, all characters must be digits, although * * there can also be no more than one decimal point. */ int i, len, num_decimal_points = 0; len = strlen(ptr); for (i = 0; i < len; i++) { if ((ptr[i] < '0' || ptr[i] > '9')) { if (ptr[i] != '.') { return FALSE; } num_decimal_points++; if (num_decimal_points > 1) { return FALSE; } } } return TRUE; } static int find_constrained_clock(char * ptr) { /* Given a string ptr, 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(ptr, g_sdc->constrained_clocks[index].name) == 0) { return index; } } return -1; } static int find_cc_constraint(char * source_clock_name, char * sink_clock_name) { /* Given a pair of source and sink clock domains, find out if there's an override constraint between them. If there is, return the index in g_sdc->cc_constraints; if there is not, return -1. */ int icc, isource, isink; for (icc = 0; icc < g_sdc->num_cc_constraints; icc++) { for (isource = 0; isource < g_sdc->cc_constraints[icc].num_source; isource++) { if (strcmp(g_sdc->cc_constraints[icc].source_list[isource], source_clock_name) == 0) { for (isink = 0; isink < g_sdc->cc_constraints[icc].num_sink; isink++) { if (strcmp(g_sdc->cc_constraints[icc].sink_list[isink], sink_clock_name) == 0) { return icc; } } } } } return -1; } static void add_override_constraint(char ** from_list, int num_from, char ** to_list, int num_to, float constraint, int num_multicycles, boolean domain_level_from, boolean domain_level_to, boolean make_copies) { /* Add a special-case constraint to override the default, calculated timing constraint, to one of four arrays depending on whether it's coming from/to a flip-flop or an entire clock domain. If make_copies is true, we make a copy of from_list and to_list for this override constraint entry; if false, we just set the override constraint entry to point to the existing list. The latter is more efficient, but it's almost impossible to free multiple identical pointers without freeing the same thing twice and causing an error. */ t_override_constraint ** constraint_array; /* Because we are reallocating the array and possibly changing its address, we need to modify it through a reference. */ int num_constraints, i; if (domain_level_from) { if (domain_level_to) { /* Clock-to-clock constraint */ constraint_array = &g_sdc->cc_constraints; num_constraints = ++g_sdc->num_cc_constraints; } else { /* Clock-to-flipflop constraint */ constraint_array = &g_sdc->cf_constraints; num_constraints = ++g_sdc->num_cf_constraints; } } else { if (domain_level_to) { /* Flipflop-to-clock constraint */ constraint_array = &g_sdc->fc_constraints; num_constraints = ++g_sdc->num_fc_constraints; } else { /* Flipflop-to-flipflop constraint */ constraint_array = &g_sdc->ff_constraints; num_constraints = ++g_sdc->num_ff_constraints; } } *constraint_array = (t_override_constraint *) my_realloc(*constraint_array, num_constraints * sizeof(t_override_constraint)); if (make_copies) { /* Copy from_list and to_list to constraint_array[num_constraints - 1].source_list and .sink_list. */ (*constraint_array)[num_constraints - 1].source_list = (char **) my_malloc(num_from * sizeof(char *)); (*constraint_array)[num_constraints - 1].sink_list = (char **) my_malloc(num_to * sizeof(char *)); for (i = 0; i < num_from; i++) { (*constraint_array)[num_constraints - 1].source_list[i] = my_strdup(from_list[i]); } for (i = 0; i < num_to; i++) { (*constraint_array)[num_constraints - 1].sink_list[i] = my_strdup(to_list[i]); } } else { /* Just set constraint array to point to from_list and to_list. */ (*constraint_array)[num_constraints - 1].source_list = from_list; (*constraint_array)[num_constraints - 1].sink_list = to_list; } (*constraint_array)[num_constraints - 1].num_source = num_from; (*constraint_array)[num_constraints - 1].num_sink = num_to; (*constraint_array)[num_constraints - 1].constraint = constraint; (*constraint_array)[num_constraints - 1].num_multicycles = num_multicycles; (*constraint_array)[num_constraints - 1].file_line_number = file_line_number; /* global var */ } static float calculate_constraint(t_sdc_clock source_domain, t_sdc_clock sink_domain) { /* Given information from the SDC file about the period and offset of two clocks, * * determine the implied setup-time constraint between them via edge counting. */ int source_period, sink_period, source_rising_edge, sink_rising_edge, lcm_period, num_source_edges, num_sink_edges, * source_edges, * sink_edges, i, j, time, constraint_as_int; float constraint; /* If the source and sink domains have the same period and edges, the constraint is just the common clock period. */ if (fabs(source_domain.period - sink_domain.period) < EPSILON && fabs(source_domain.rising_edge - sink_domain.rising_edge) < EPSILON && fabs(source_domain.falling_edge - sink_domain.falling_edge) < EPSILON) { return source_domain.period; /* or, equivalently, sink_domain.period */ } /* If either period is 0, the constraint is 0. */ if (source_domain.period < EPSILON || sink_domain.period < EPSILON) { return 0.; } /* Multiply periods and edges by 1000 and round down * * to the nearest integer, to avoid messy decimals. */ source_period = static_cast(source_domain.period * 1000); sink_period = static_cast(sink_domain.period * 1000); source_rising_edge = static_cast(source_domain.rising_edge * 1000); sink_rising_edge = static_cast(sink_domain.rising_edge * 1000); /* If we get here, we have to use edge counting. Find the LCM of the two periods. * * This determines how long it takes before the pattern of the two clocks starts repeating. */ for (lcm_period = 1; lcm_period % source_period != 0 || lcm_period % sink_period != 0; lcm_period++) ; /* Create an array of positive edges for each clock over one LCM clock period. */ num_source_edges = lcm_period/source_period + 1; num_sink_edges = lcm_period/sink_period + 1; source_edges = (int *) my_malloc((num_source_edges + 1) * sizeof(int)); sink_edges = (int *) my_malloc((num_sink_edges + 1) * sizeof(int)); for (i = 0, time = source_rising_edge; i < num_source_edges + 1; i++) { source_edges[i] = time; time += source_period; } for (i = 0, time = sink_rising_edge; i < num_sink_edges + 1; i++) { sink_edges[i] = time; time += sink_period; } /* Compare every edge in source_edges with every edge in sink_edges. * * The lowest STRICTLY POSITIVE difference between a sink edge and a source edge * * gives us the set-up time constraint. */ constraint_as_int = INT_MAX; /* constraint starts off at +ve infinity so that everything will be less than it */ for (i = 0; i < num_source_edges + 1; i++) { for (j = 0; j < num_sink_edges + 1; j++) { if (sink_edges[j] > source_edges[i]) { constraint_as_int = std::min(constraint_as_int, sink_edges[j] - source_edges[i]); } } } /* Divide by 1000 again and turn the constraint back into a float, and clean up memory. */ constraint = constraint_as_int / 1000.; free(source_edges); free(sink_edges); return constraint; } static boolean regex_match (char * string, char * regular_expression) { /* Given a string and a regular expression, return TRUE if there's a match, FALSE if not. Print an error and exit if regular_expression is invalid. */ const char * error; assert(string && regular_expression); /* The regex library reports a match if regular_expression is a substring of string AND not equal to string. This is not appropriate for our purposes. For example, we'd get both "clock" and "clock2" matching the regular expression "clock". We have to manually return that there's no match in this special case. */ if (strstr(string, regular_expression) && strcmp(string, regular_expression) != 0) return FALSE; if (strcmp(regular_expression, "*") == 0) return TRUE; /* The regex library hangs if it is fed "*" as a regular expression. */ error = slre_match((enum slre_option) 0, regular_expression, string, strlen(string)); if (!error) return TRUE; else if (strcmp(error, "No match") == 0) return FALSE; else { vpr_printf(TIO_MESSAGE_ERROR, "[SDC line %d] Error matching regular expression \"%s\".\n", file_line_number, regular_expression); exit(1); } } void free_sdc_related_structs(void) { if (!g_sdc) return; free_override_constraint(g_sdc->cc_constraints, g_sdc->num_cc_constraints); /* Should already have been freed in process_constraints() */ free_override_constraint(g_sdc->cf_constraints, g_sdc->num_cf_constraints); free_override_constraint(g_sdc->fc_constraints, g_sdc->num_fc_constraints); free_override_constraint(g_sdc->ff_constraints, g_sdc->num_ff_constraints); free_io_constraint(g_sdc->constrained_inputs, g_sdc->num_constrained_inputs); free_io_constraint(g_sdc->constrained_outputs, g_sdc->num_constrained_outputs); free_clock_constraint(g_sdc->constrained_clocks, g_sdc->num_constrained_clocks); free_matrix(g_sdc->domain_constraint, 0, g_sdc->num_constrained_clocks - 1, 0, sizeof(float)); free(g_sdc); g_sdc = NULL; } void free_override_constraint(t_override_constraint *& constraint_array, int num_constraints) { int i, j; if (!constraint_array) return; for (i = 0; i < num_constraints; i++) { for (j = 0; j < constraint_array[i].num_source; j++) { free(constraint_array[i].source_list[j]); constraint_array[i].source_list[j] = NULL; } for (j = 0; j < constraint_array[i].num_sink; j++) { free(constraint_array[i].sink_list[j]); constraint_array[i].sink_list[j] = NULL; } free(constraint_array[i].source_list); free(constraint_array[i].sink_list); } free(constraint_array); constraint_array = NULL; } static void free_io_constraint(t_io *& io_array, int num_ios) { int i; for (i = 0; i < num_ios; i++) { free(io_array[i].name); free(io_array[i].clock_name); } free(io_array); io_array = NULL; } static void free_clock_constraint(t_clock *& clock_array, int num_clocks) { int i; for (i = 0; i < num_clocks; i++) { free(clock_array[i].name); } free(clock_array); clock_array = NULL; }