1338 lines
61 KiB
C
1338 lines
61 KiB
C
|
#include <string.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <limits.h>
|
||
|
#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 <circuitname>.sdc
|
||
|
(unless overridden with --sdc_file <filename.sdc> 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 <float> [-waveform {rising_edge falling_edge}] <netlist clock list or regexes>
|
||
|
or create_clock -period <float> [-waveform {rising_edge falling_edge}] -name <virtual clock 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 {<clock list or regexes>} -group {<clock list or regexes>} [-group {<clock list or regexes>} ...] */
|
||
|
|
||
|
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 <clock list or regexes> -to <clock list or regexes> */
|
||
|
|
||
|
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 <clock/flip-flop_list>'.\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 <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>'.\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 <delay> -from <clock list or regexes> -to <clock list or regexes> */
|
||
|
|
||
|
/* 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 <clock/flip-flop_list>' 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 <clock/flip-flop_list>' 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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock list or regexes> -to <clock list or regexes> <num_multicycles> */
|
||
|
|
||
|
/* 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 <clock/flip-flop_list>' 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 <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>' after '-from <clock/flip-flop_list>'.\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 <clock/flip-flop_list>'.\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 <virtual or netlist clock> -max <max_delay> [get_ports {<I/O port list or regexes>}] */
|
||
|
|
||
|
/* 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 <virtual or netlist clock name>'.\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 <virtual or netlist clock name>' must be directly followed by '-max <maximum_input_delay>'.\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 <max_input_delay>'.\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 <virtual or netlist clock> -max <max_delay> [get_ports {<I/O port list or regexes>}] */
|
||
|
|
||
|
/* 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 <virtual or netlist clock name>'.\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 <virtual or netlist clock name> must be directly followed by '-max <maximum_output_delay>'.\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 <max_output_delay>'.\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<int>(source_domain.period * 1000);
|
||
|
sink_period = static_cast<int>(sink_domain.period * 1000);
|
||
|
source_rising_edge = static_cast<int>(source_domain.rising_edge * 1000);
|
||
|
sink_rising_edge = static_cast<int>(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;
|
||
|
}
|