| #include <cstdio> | |
| #include <cstring> | |
| #include <cstdlib> | |
| #include <climits> | |
| #include <cmath> | |
| #include <cstdarg> | |
| using namespace std; | |
| #include "vtr_assert.h" | |
| #include "vtr_util.h" | |
| #include "vtr_log.h" | |
| #include "vtr_memory.h" | |
| #include "vtr_math.h" | |
| #include "vpr_types.h" | |
| #include "vpr_error.h" | |
| #include "globals.h" | |
| #include "atom_netlist.h" | |
| #include "read_sdc.h" | |
| #include "read_blif.h" | |
| #include "path_delay.h" | |
| #include "path_delay2.h" | |
| #include "slre.h" | |
| #include "sdcparse.hpp" | |
| /***************************** 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, timing_ctx.sdc. | |
| One of the two key output data structures within is timing_ctx.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" timing_ctx.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 timing_ctx.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 timing_ctx.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 timing_ctx.sdc->cf_constraints, | |
| timing_ctx.sdc->fc_constraints and timing_ctx.sdc->ff_constraints (depending on whether the source, sink or neither | |
| of the two is a clock domain).Unlike timing_ctx.sdc->cc_constraints, they are placed on the timing | |
| graph during timing analysis instead of going into timing_ctx.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 timing_ctx.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 timing_ctx.sdc->cc_constraints. */ | |
| /****************** Types local to this module **************************/ | |
| struct t_sdc_clock { | |
| char * name; | |
| float period; | |
| float rising_edge; | |
| float falling_edge; | |
| }; | |
| /* Stores the name, period and offset of each constrained clock. */ | |
| struct t_sdc_exclusive_group { | |
| char ** clock_names; | |
| int num_clock_names; | |
| }; | |
| /* 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 = nullptr; /* 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 */ | |
| static std::string sdc_file_name = "<default_SDC>.sdc"; /* Name of SDC file */ | |
| /***************** Subroutines local to this module *************************/ | |
| static void alloc_and_load_netlist_clocks_and_ios(); | |
| static void use_default_timing_constraints(); | |
| static void count_netlist_clocks_as_constrained_clocks(); | |
| static void add_clock(std::string net_name); | |
| 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, bool domain_level_from, bool domain_level_to, | |
| bool make_copies); | |
| static int find_cc_constraint(char * source_clock_domain, char * sink_clock_domain); | |
| static bool regex_match (const char *string, const 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); | |
| static bool apply_create_clock(const sdcparse::CreateClock& sdc_create_clock, int lineno); | |
| static bool apply_set_clock_groups(const sdcparse::SetClockGroups& sdc_set_clock_groups, int lineno); | |
| static bool apply_set_false_path(const sdcparse::SetFalsePath& sdc_set_false_path); | |
| static bool apply_set_min_max_delay(const sdcparse::SetMinMaxDelay& sdc_set_min_max_delay); | |
| static bool apply_set_multicycle_path(const sdcparse::SetMulticyclePath& sdc_set_multicycle_path); | |
| static bool apply_set_io_delay(const sdcparse::SetIoDelay& sdc_set_io_delay, int lineno); | |
| //TODO | |
| //static bool apply_set_clock_uncertainty(const sdcparse::SetClockUncertainty& sdc_set_clock_uncertainty); | |
| //static bool apply_set_clock_latency(const sdcparse::SetClockLatency& sdc_set_clock_latency); | |
| //static bool apply_set_timing_derate(const sdcparse::SetTimingDerate& sdc_set_timing_derate); | |
| static bool is_valid_clock_name(const char* clock_name); | |
| static bool build_from_to_lists(char ***from_list, int *num_from, bool* domain_level_from, | |
| char ***to_list, int *num_to, bool* domain_level_to, | |
| const sdcparse::StringGroup& from_group, const sdcparse::StringGroup& to_group); | |
| void vpr_sdc_error(const int line_number, const std::string& near_text, const std::string& msg); | |
| /********************* Class definitions *******************************/ | |
| class SdcCallback : public sdcparse::Callback { | |
| public: //sdcparse::Callback interface | |
| //Start of parsing | |
| void start_parse() override {} | |
| //Sets current filename | |
| void filename(std::string fname) override { fname_ = fname; } | |
| //Sets current line number | |
| void lineno(int line_num) override { lineno_ = line_num; } | |
| //Individual commands | |
| void create_clock(const sdcparse::CreateClock& cmd) override { | |
| got_commands_ = true; | |
| apply_create_clock(cmd, lineno_); | |
| } | |
| void set_io_delay(const sdcparse::SetIoDelay& cmd) override { | |
| got_commands_ = true; | |
| apply_set_io_delay(cmd, lineno_); | |
| } | |
| void set_clock_groups(const sdcparse::SetClockGroups& cmd) override { | |
| got_commands_ = true; | |
| apply_set_clock_groups(cmd, lineno_); | |
| } | |
| void set_false_path(const sdcparse::SetFalsePath& cmd) override { | |
| got_commands_ = true; | |
| apply_set_false_path(cmd); | |
| } | |
| void set_min_max_delay(const sdcparse::SetMinMaxDelay& cmd) override { | |
| got_commands_ = true; | |
| apply_set_min_max_delay(cmd); | |
| } | |
| void set_multicycle_path(const sdcparse::SetMulticyclePath& cmd) override { | |
| got_commands_ = true; | |
| apply_set_multicycle_path(cmd); | |
| } | |
| void set_clock_uncertainty(const sdcparse::SetClockUncertainty& /*cmd*/) override { | |
| got_commands_ = true; | |
| vpr_sdc_error(lineno_, "", "set_clock_uncertainty currently unsupported"); | |
| } | |
| void set_clock_latency(const sdcparse::SetClockLatency& /*cmd*/) override { | |
| got_commands_ = true; | |
| vpr_sdc_error(lineno_, "", "set_clock_latency currently unsupported"); | |
| } | |
| void set_disable_timing(const sdcparse::SetDisableTiming& /*cmd*/) override { | |
| got_commands_ = true; | |
| vpr_sdc_error(lineno_, "", "set_disable_timing currently unsupported"); | |
| } | |
| void set_timing_derate(const sdcparse::SetTimingDerate& /*cmd*/) override { | |
| got_commands_ = true; | |
| vpr_sdc_error(lineno_, "", "set_timing_derate currently unsupported"); | |
| } | |
| //End of parsing | |
| void finish_parse() override {} | |
| //Error during parsing | |
| void parse_error(const int curr_lineno, const std::string& near_text, const std::string& msg) override { | |
| vpr_sdc_error(curr_lineno, near_text, msg); | |
| } | |
| public: | |
| bool got_commands() { return got_commands_; } | |
| private: | |
| bool got_commands_ = false; | |
| std::string fname_; | |
| int lineno_ = -1; | |
| }; | |
| /********************* Subroutine definitions *******************************/ | |
| void read_sdc(t_timing_inf timing_inf) { | |
| int source_clock_domain, sink_clock_domain, iinput, ioutput, icc, isource, isink; | |
| auto& timing_ctx = g_vpr_ctx.mutable_timing(); | |
| /* Make sure we haven't called this subroutine before. */ | |
| VTR_ASSERT(!timing_ctx.sdc); | |
| /* Allocate container structure for SDC constraints. */ | |
| timing_ctx.sdc = (t_timing_constraints *) vtr::calloc(1, sizeof(t_timing_constraints)); | |
| #ifndef ENABLE_CLASSIC_VPR_STA | |
| return; | |
| #endif | |
| /* 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) { | |
| vtr::printf_info("\n"); | |
| vtr::printf_info("Timing analysis off; using default timing constraints.\n"); | |
| use_default_timing_constraints(); | |
| return; | |
| } | |
| if ((sdc = fopen(timing_inf.SDCFile.c_str(), "r")) == nullptr) { | |
| vtr::printf_info("\n"); | |
| vtr::printf_info("SDC file '%s' blank or not found.\n", timing_inf.SDCFile.c_str()); | |
| use_default_timing_constraints(); | |
| return; | |
| } | |
| /* Now we have an SDC file. */ | |
| /* Save name of SDC file for error outputs */ | |
| sdc_file_name = timing_inf.SDCFile; | |
| /* 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. */ | |
| SdcCallback sdc_callback; | |
| sdcparse::sdc_parse_file(sdc, sdc_callback, sdc_file_name.c_str()); | |
| if (!sdc_callback.got_commands()) { /* blank file or only comments found */ | |
| vtr::printf_info("\n"); | |
| vtr::printf_info("SDC file '%s' blank or not found.\n", timing_inf.SDCFile.c_str()); | |
| use_default_timing_constraints(); | |
| free(netlist_clocks); | |
| free(netlist_ios); | |
| return; | |
| } | |
| /* Make sure that all virtual clocks referenced in timing_ctx.sdc->constrained_inputs and timing_ctx.sdc->constrained_outputs have been constrained. */ | |
| for (iinput = 0; iinput < timing_ctx.sdc->num_constrained_inputs; iinput++) { | |
| if ((find_constrained_clock(timing_ctx.sdc->constrained_inputs[iinput].clock_name)) == -1) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), timing_ctx.sdc->constrained_inputs[iinput].file_line_number, | |
| "Input %s is associated with an unconstrained clock %s.\n", | |
| timing_ctx.sdc->constrained_inputs[iinput].name, | |
| timing_ctx.sdc->constrained_inputs[iinput].clock_name); | |
| } | |
| } | |
| for (ioutput = 0; ioutput < timing_ctx.sdc->num_constrained_outputs; ioutput++) { | |
| if ((find_constrained_clock(timing_ctx.sdc->constrained_outputs[ioutput].clock_name)) == -1) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), timing_ctx.sdc->constrained_inputs[iinput].file_line_number, | |
| "Output %s is associated with an unconstrained clock %s.\n", | |
| timing_ctx.sdc->constrained_outputs[ioutput].name, | |
| timing_ctx.sdc->constrained_outputs[ioutput].clock_name); | |
| } | |
| } | |
| /* Make sure that all clocks referenced in timing_ctx.sdc->cc_constraints have been constrained. */ | |
| for (icc = 0; icc < timing_ctx.sdc->num_cc_constraints; icc++) { | |
| for (isource = 0; isource < timing_ctx.sdc->cc_constraints[icc].num_source; isource++) { | |
| if ((find_constrained_clock(timing_ctx.sdc->cc_constraints[icc].source_list[isource])) == -1) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), timing_ctx.sdc->cc_constraints[icc].file_line_number, | |
| "Token %s is not a constrained clock.\n", | |
| timing_ctx.sdc->cc_constraints[icc].source_list[isource]); | |
| } | |
| } | |
| for (isink = 0; isink < timing_ctx.sdc->cc_constraints[icc].num_sink; isink++) { | |
| if ((find_constrained_clock(timing_ctx.sdc->cc_constraints[icc].sink_list[isink])) == -1) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), timing_ctx.sdc->cc_constraints[icc].file_line_number, | |
| "Token %s is not a constrained clock.\n", | |
| timing_ctx.sdc->cc_constraints[icc].sink_list[isink]); | |
| } | |
| } | |
| } | |
| /* Allocate matrix of timing constraints [0..timing_ctx.sdc->num_constrained_clocks-1][0..timing_ctx.sdc->num_constrained_clocks-1] and initialize to 0 */ | |
| size_t num_constrained_clocks = timing_ctx.sdc->num_constrained_clocks; | |
| timing_ctx.sdc->domain_constraint = vtr::Matrix<float>({num_constrained_clocks, num_constrained_clocks}); | |
| /* 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 < timing_ctx.sdc->num_constrained_clocks; source_clock_domain++) { | |
| for (sink_clock_domain = 0; sink_clock_domain < timing_ctx.sdc->num_constrained_clocks; sink_clock_domain++) { | |
| if ((icc = find_cc_constraint(timing_ctx.sdc->constrained_clocks[source_clock_domain].name, timing_ctx.sdc->constrained_clocks[sink_clock_domain].name)) != -1) { | |
| if (timing_ctx.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. */ | |
| timing_ctx.sdc->domain_constraint[source_clock_domain][sink_clock_domain] = timing_ctx.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. */ | |
| timing_ctx.sdc->domain_constraint[source_clock_domain][sink_clock_domain] = | |
| calculate_constraint(sdc_clocks[source_clock_domain], sdc_clocks[sink_clock_domain]) | |
| + (timing_ctx.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. */ | |
| timing_ctx.sdc->domain_constraint[source_clock_domain][sink_clock_domain] = | |
| calculate_constraint(sdc_clocks[source_clock_domain], sdc_clocks[sink_clock_domain]); | |
| } | |
| } | |
| } | |
| vtr::printf_info("\n"); | |
| vtr::printf_info("SDC file '%s' parsed successfully.\n", | |
| timing_inf.SDCFile.c_str() ); | |
| vtr::printf_info("%d clocks (including virtual clocks), %d inputs and %d outputs were constrained.\n", | |
| timing_ctx.sdc->num_constrained_clocks, timing_ctx.sdc->num_constrained_inputs, timing_ctx.sdc->num_constrained_outputs); | |
| vtr::printf_info("\n"); | |
| /* Since all the information we need is stored in timing_ctx.sdc->domain_constraint, timing_ctx.sdc->constrained_clocks, | |
| and constrained_ios, free other data structures used in this routine */ | |
| for (int iclk = 0; iclk < timing_ctx.sdc->num_constrained_clocks; iclk++) { | |
| free(sdc_clocks[iclk].name); | |
| } | |
| free(sdc_clocks); | |
| free(netlist_clocks); | |
| for(int i = 0; i < num_netlist_ios; ++i) { | |
| free(netlist_ios[i]); | |
| } | |
| free(netlist_ios); | |
| fclose(sdc); | |
| return; | |
| } | |
| /* | |
| * Override the default error function in libsdcparse so that it throws | |
| * vpr style errors. | |
| */ | |
| void vpr_sdc_error(const int line_number, const std::string& /*near_text*/, const std::string& msg) { | |
| vpr_throw(VPR_ERROR_SDC, get_sdc_file_name(), line_number, msg.c_str()); | |
| } | |
| static bool apply_create_clock(const sdcparse::CreateClock& sdc_create_clock, int lineno) { | |
| bool found; | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| if(sdc_create_clock.is_virtual) { | |
| /* Store the clock's name, period and edges in the local array sdc_clocks. */ | |
| sdc_clocks = (t_sdc_clock *) vtr::realloc(sdc_clocks, ++timing_ctx.sdc->num_constrained_clocks * sizeof(t_sdc_clock)); | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = vtr::strdup(sdc_create_clock.name.c_str()); | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].period = sdc_create_clock.period; | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].rising_edge = sdc_create_clock.rise_edge; | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].falling_edge = sdc_create_clock.fall_edge; | |
| /* Also store the clock's name, and the fact that it is not a netlist clock, in timing_ctx.sdc->constrained_clocks. */ | |
| timing_ctx.sdc->constrained_clocks = (t_clock *) vtr::realloc (timing_ctx.sdc->constrained_clocks, timing_ctx.sdc->num_constrained_clocks * sizeof(t_clock)); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = vtr::strdup(sdc_create_clock.name.c_str()); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].is_netlist_clock = false; | |
| } else { | |
| VTR_ASSERT(!sdc_create_clock.is_virtual); | |
| for(size_t itarget = 0; itarget < sdc_create_clock.targets.strings.size(); itarget++) { | |
| /* 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 bool found. */ | |
| found = false; | |
| for (int iclock = 0; iclock < num_netlist_clocks; iclock++) { | |
| if (regex_match(netlist_clocks[iclock], sdc_create_clock.targets.strings[itarget].c_str())) { | |
| /* 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 *) vtr::realloc(sdc_clocks, ++timing_ctx.sdc->num_constrained_clocks * sizeof(t_sdc_clock)); | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = netlist_clocks[iclock]; | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].period = sdc_create_clock.period; | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].rising_edge = sdc_create_clock.rise_edge; | |
| sdc_clocks[timing_ctx.sdc->num_constrained_clocks - 1].falling_edge = sdc_create_clock.fall_edge; | |
| /* Also store the clock's name, and the fact that it is a netlist clock, in timing_ctx.sdc->constrained_clocks. */ | |
| timing_ctx.sdc->constrained_clocks = (t_clock *) vtr::realloc (timing_ctx.sdc->constrained_clocks, timing_ctx.sdc->num_constrained_clocks * sizeof(t_clock)); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = vtr::strdup(netlist_clocks[iclock]); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].is_netlist_clock = true; | |
| /* Fanout will be filled out once the timing graph has been constructed. */ | |
| } | |
| } | |
| if (!found) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), lineno, | |
| "Clock name or regular expression does not correspond to any nets.\n" | |
| "If you'd like to create a virtual clock, use the '-name' keyword.\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| static bool apply_set_clock_groups(const sdcparse::SetClockGroups& sdc_set_clock_groups, int lineno) { | |
| bool found; | |
| int iclock; | |
| int num_exclusive_groups = 0; | |
| t_sdc_exclusive_group *exclusive_groups = nullptr; | |
| VTR_ASSERT(sdc_set_clock_groups.clock_groups.size() >= 2); //Should have already been caught by parser | |
| VTR_ASSERT(sdc_set_clock_groups.type == sdcparse::ClockGroupsType::EXCLUSIVE); //Currently only form supported | |
| for(size_t igroup = 0; igroup < sdc_set_clock_groups.clock_groups.size(); igroup++) { | |
| const sdcparse::StringGroup& clock_group = sdc_set_clock_groups.clock_groups[igroup]; | |
| /* Create a new entry in exclusive groups */ | |
| exclusive_groups = (t_sdc_exclusive_group *) vtr::realloc( | |
| exclusive_groups, ++num_exclusive_groups * sizeof(t_sdc_exclusive_group)); | |
| exclusive_groups[num_exclusive_groups - 1].clock_names = nullptr; | |
| exclusive_groups[num_exclusive_groups - 1].num_clock_names = 0; | |
| for(size_t iclk_name = 0; iclk_name < clock_group.strings.size(); iclk_name++) { | |
| const char* clk_name = clock_group.strings[iclk_name].c_str(); | |
| /* Check the regex clk_name 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], clk_name)) { | |
| found = true; | |
| exclusive_groups[num_exclusive_groups - 1].clock_names = (char **) vtr::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] = | |
| vtr::strdup(netlist_clocks[iclock]); | |
| } | |
| } | |
| if (!found) { | |
| if(!is_valid_clock_name(clk_name)) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), lineno, | |
| "Clock name '%s' does not match to any known clock.\n", | |
| clk_name); | |
| } else { | |
| /* The clock_name is a valid non-netlist clock (i.e. a virtual clock), so add it to the list. */ | |
| exclusive_groups[num_exclusive_groups - 1].clock_names = (char **) vtr::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] = vtr::strdup(clk_name); | |
| } | |
| } | |
| } | |
| } | |
| /* 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 (int i = 0; i < num_exclusive_groups; i++) { | |
| for (int 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 (int i = 0; i < num_exclusive_groups; i++) { | |
| for (int 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; | |
| } | |
| static bool apply_set_false_path(const sdcparse::SetFalsePath& sdc_set_false_path) { | |
| bool domain_level_from = false, domain_level_to = false; | |
| int num_from = 0, num_to = 0; | |
| char **from_list = nullptr, **to_list = nullptr; | |
| build_from_to_lists(&from_list, &num_from, &domain_level_from, | |
| &to_list, &num_to, &domain_level_to, | |
| sdc_set_false_path.from, sdc_set_false_path.to); | |
| /* 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 = nullptr, to_list = nullptr; | |
| return true; | |
| } | |
| static bool apply_set_min_max_delay(const sdcparse::SetMinMaxDelay& sdc_set_min_max_delay) { | |
| /* Basically the same as apply_set_false_path, except we get a specific delay value for the constraint. */ | |
| bool domain_level_from = false, domain_level_to = false; | |
| int num_from = 0, num_to = 0; | |
| char **from_list = nullptr, **to_list = nullptr; | |
| if(sdc_set_min_max_delay.type != sdcparse::MinMaxType::MAX) { | |
| vpr_sdc_error(-1, "", "Only set_max_delay currently supported"); | |
| } | |
| build_from_to_lists(&from_list, &num_from, &domain_level_from, | |
| &to_list, &num_to, &domain_level_to, | |
| sdc_set_min_max_delay.from, sdc_set_min_max_delay.to); | |
| /* 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, sdc_set_min_max_delay.value, | |
| 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 = nullptr, to_list = nullptr; | |
| return true; | |
| } | |
| static bool apply_set_multicycle_path(const sdcparse::SetMulticyclePath& sdc_set_multicycle_path) { | |
| bool domain_level_from = false, domain_level_to = false; | |
| int num_from = 0, num_to = 0; | |
| char **from_list = nullptr, **to_list = nullptr; | |
| VTR_ASSERT(sdc_set_multicycle_path.is_setup && !sdc_set_multicycle_path.is_hold); //Currently only form supported | |
| build_from_to_lists(&from_list, &num_from, &domain_level_from, | |
| &to_list, &num_to, &domain_level_to, | |
| sdc_set_multicycle_path.from, sdc_set_multicycle_path.to); | |
| /* 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 */, | |
| sdc_set_multicycle_path.mcp_value, 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 = nullptr, to_list = nullptr; | |
| return true; | |
| } | |
| static bool apply_set_io_delay(const sdcparse::SetIoDelay& sdc_set_io_delay, int lineno) { | |
| bool found; | |
| const char* io_type; | |
| const char* clock_name = sdc_set_io_delay.clock_name.c_str(); | |
| //Support for non-standard wildcard clock name for set_input_delay/set_output_dealy | |
| //commands, provided it is unambiguous (i.e. there is only one netlist clock) | |
| if(num_netlist_clocks == 1 && strcmp(clock_name, "*") == 0) { | |
| clock_name = netlist_clocks[0]; | |
| } | |
| //Verify that the provided clock name is valid | |
| if(!is_valid_clock_name(clock_name)) { | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), lineno, | |
| "Clock name '%s' does not match to any known clock.\n", | |
| clock_name); | |
| } | |
| /* | |
| * Add each regular expression match we find to the list of constrained | |
| * inputs (outputs) and give each entry the appropraite clock name and | |
| * max_delay | |
| */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| const sdcparse::StringGroup& port_group = sdc_set_io_delay.target_ports; | |
| for (size_t iport = 0; iport < port_group.strings.size(); iport++) { | |
| found = false; | |
| for (int iio = 0; iio < num_netlist_ios; iio++) { | |
| /* See if the regular expression 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 bool found. */ | |
| if (regex_match(netlist_ios[iio], port_group.strings[iport].c_str())) { | |
| if(sdc_set_io_delay.type == sdcparse::IoDelayType::INPUT) { | |
| /* We've found a new input! */ | |
| timing_ctx.sdc->num_constrained_inputs++; | |
| found = true; | |
| /* Fill in input information in the permanent array timing_ctx.sdc->constrained_inputs. */ | |
| timing_ctx.sdc->constrained_inputs = (t_io *) vtr::realloc (timing_ctx.sdc->constrained_inputs, timing_ctx.sdc->num_constrained_inputs * sizeof(t_io)); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].name = vtr::strdup(netlist_ios[iio]); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].clock_name = vtr::strdup(clock_name); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].delay = sdc_set_io_delay.delay; | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].file_line_number = lineno; | |
| } else { | |
| VTR_ASSERT(sdc_set_io_delay.type == sdcparse::IoDelayType::OUTPUT); | |
| /* We've found a new output! */ | |
| timing_ctx.sdc->num_constrained_outputs++; | |
| found = true; | |
| /* Fill in output information in the permanent array timing_ctx.sdc->constrained_outputs. */ | |
| timing_ctx.sdc->constrained_outputs = (t_io *) vtr::realloc (timing_ctx.sdc->constrained_outputs, timing_ctx.sdc->num_constrained_outputs * sizeof(t_io)); | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].name = vtr::strdup(netlist_ios[iio]); | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].clock_name = vtr::strdup(clock_name); | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].delay = sdc_set_io_delay.delay; | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].file_line_number = lineno; | |
| } | |
| } | |
| } | |
| if (!found) { | |
| if(sdc_set_io_delay.type == sdcparse::IoDelayType::INPUT) { | |
| io_type = "Input"; | |
| } else { | |
| VTR_ASSERT(sdc_set_io_delay.type == sdcparse::IoDelayType::OUTPUT); | |
| io_type = "Output"; | |
| } | |
| vpr_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), lineno, | |
| "%s name or regular expression \"%s\" does not correspond to any nets.\n", io_type, port_group.strings[iport].c_str()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| static bool is_valid_clock_name(const char* clock_name) { | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| bool found = false; | |
| for(int iclk = 0; iclk < timing_ctx.sdc->num_constrained_clocks; iclk++) { | |
| if(strcmp(timing_ctx.sdc->constrained_clocks[iclk].name, clock_name) == 0) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| return found; | |
| } | |
| static bool build_from_to_lists(char ***from_list, int *num_from, bool* domain_level_from, | |
| char ***to_list, int *num_to, bool* domain_level_to, | |
| const sdcparse::StringGroup& from_group, const sdcparse::StringGroup& to_group) { | |
| /* | |
| * Source 'from' objects/clocks | |
| */ | |
| //Are we working with a set of clock domains? | |
| if(from_group.type == sdcparse::StringGroupType::CLOCK) { | |
| *domain_level_from = true; | |
| } | |
| for(size_t i = 0; i < from_group.strings.size(); i++) { | |
| /* Keep adding clock names to from_list */ | |
| (*from_list) = (char **) vtr::realloc((*from_list), ++(*num_from) * sizeof(**from_list)); | |
| (*from_list)[(*num_from) - 1] = vtr::strdup(from_group.strings[i].c_str()); | |
| } | |
| /* | |
| * Target 'to' object/clocsk | |
| */ | |
| //Are we working with a set of clock domains? | |
| if(to_group.type == sdcparse::StringGroupType::CLOCK) { | |
| *domain_level_to = true; | |
| } | |
| for(size_t i = 0; i < to_group.strings.size(); i++) { | |
| /* Keep adding clock names to to_list */ | |
| (*to_list) = (char **) vtr::realloc((*to_list), ++(*num_to) * sizeof(**to_list)); | |
| (*to_list)[(*num_to) - 1] = vtr::strdup(to_group.strings[i].c_str()); | |
| } | |
| return true; | |
| } | |
| static void use_default_timing_constraints() { | |
| int source_clock_domain, sink_clock_domain; | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| /* 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 (timing_ctx.sdc->num_constrained_clocks <= 1) { | |
| /* Create one constrained clock with period 0... */ | |
| timing_ctx.sdc->domain_constraint = vtr::Matrix<float>({1,1}); | |
| timing_ctx.sdc->domain_constraint[0][0] = 0.; | |
| vtr::printf_info("\n"); | |
| if (timing_ctx.sdc->num_constrained_clocks == 0) { | |
| /* We need to create a virtual clock to constrain I/Os on. */ | |
| timing_ctx.sdc->num_constrained_clocks = 1; | |
| timing_ctx.sdc->constrained_clocks = (t_clock *) vtr::malloc(sizeof(t_clock)); | |
| timing_ctx.sdc->constrained_clocks[0].name = vtr::strdup("virtual_io_clock"); | |
| timing_ctx.sdc->constrained_clocks[0].is_netlist_clock = false; | |
| /* Constrain all I/Os on the virtual clock, with I/O delay 0. */ | |
| count_netlist_ios_as_constrained_ios(timing_ctx.sdc->constrained_clocks[0].name, 0.); | |
| vtr::printf_info("Defaulting to: constrain all %d inputs and %d outputs on a virtual external clock.\n", | |
| timing_ctx.sdc->num_constrained_inputs, timing_ctx.sdc->num_constrained_outputs); | |
| vtr::printf_info("Optimize this virtual clock to run as fast as possible.\n"); | |
| } else { | |
| /* Constrain all I/Os on the single netlist clock, with I/O delay 0. */ | |
| count_netlist_ios_as_constrained_ios(timing_ctx.sdc->constrained_clocks[0].name, 0.); | |
| vtr::printf_info("Defaulting to: constrain all %d inputs and %d outputs on the netlist clock.\n", | |
| timing_ctx.sdc->num_constrained_inputs, timing_ctx.sdc->num_constrained_outputs); | |
| vtr::printf_info("Optimize this clock to run as fast as possible.\n"); | |
| } | |
| } 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. */ | |
| timing_ctx.sdc->constrained_clocks = (t_clock *) vtr::realloc (timing_ctx.sdc->constrained_clocks, ++timing_ctx.sdc->num_constrained_clocks * sizeof(t_clock)); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = vtr::strdup("virtual_io_clock"); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].is_netlist_clock = false; | |
| count_netlist_ios_as_constrained_ios(timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name, 0.); | |
| /* Allocate matrix of timing constraints [0..timing_ctx.sdc->num_constrained_clocks-1][0..timing_ctx.sdc->num_constrained_clocks-1] */ | |
| size_t num_constrained_clocks = timing_ctx.sdc->num_constrained_clocks; | |
| timing_ctx.sdc->domain_constraint = vtr::Matrix<float>({num_constrained_clocks, num_constrained_clocks}); | |
| for (source_clock_domain = 0; source_clock_domain < timing_ctx.sdc->num_constrained_clocks; source_clock_domain++) { | |
| for (sink_clock_domain = 0; sink_clock_domain < timing_ctx.sdc->num_constrained_clocks; sink_clock_domain++) { | |
| if (source_clock_domain == sink_clock_domain || source_clock_domain == timing_ctx.sdc->num_constrained_clocks - 1 | |
| || sink_clock_domain == timing_ctx.sdc->num_constrained_clocks - 1) { | |
| timing_ctx.sdc->domain_constraint[source_clock_domain][sink_clock_domain] = 0.; | |
| } else { | |
| timing_ctx.sdc->domain_constraint[source_clock_domain][sink_clock_domain] = DO_NOT_ANALYSE; | |
| } | |
| } | |
| } | |
| vtr::printf_info("\n"); | |
| vtr::printf_info("Defaulting to: constrain all %d inputs and %d outputs on a virtual external clock;\n", | |
| timing_ctx.sdc->num_constrained_inputs, timing_ctx.sdc->num_constrained_outputs); | |
| vtr::printf_info("\tcut paths between netlist clock domains; and\n"); | |
| vtr::printf_info("\toptimize all clocks to run as fast as possible.\n"); | |
| } | |
| } | |
| static void alloc_and_load_netlist_clocks_and_ios() { | |
| std::map<const t_model*,std::vector<const t_model_ports*>> clock_gen_ports; //Records info about clock generating ports | |
| /* 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. */ | |
| auto& atom_ctx = g_vpr_ctx.atom(); | |
| for(auto blk_id : atom_ctx.nlist.blocks()) { | |
| AtomBlockType type = atom_ctx.nlist.block_type(blk_id); | |
| if(type == AtomBlockType::BLOCK) { | |
| //Save any clock generating ports on this model type | |
| const t_model* model = atom_ctx.nlist.block_model(blk_id); | |
| VTR_ASSERT(model); | |
| auto iter = clock_gen_ports.find(model); | |
| if(iter == clock_gen_ports.end()) { | |
| //First time seen, record any ports which could generate clocks | |
| for(const t_model_ports* model_port = model->outputs; model_port; model_port = model_port->next) { | |
| VTR_ASSERT(model_port->dir == OUT_PORT); | |
| if(model_port->is_clock) { | |
| //Clock generator | |
| clock_gen_ports[model].push_back(model_port); | |
| } | |
| } | |
| } | |
| //Look for connected input clocks | |
| for(auto pin_id : atom_ctx.nlist.block_clock_pins(blk_id)) { | |
| AtomNetId clk_net_id = atom_ctx.nlist.pin_net(pin_id); | |
| VTR_ASSERT(clk_net_id); | |
| std::string name = atom_ctx.nlist.net_name(clk_net_id); | |
| /* Now that we've found a clock, let's see if we've counted it already */ | |
| bool found = false; | |
| for (int i = 0; !found && i < num_netlist_clocks; i++) { | |
| if (netlist_clocks[i] == name) { | |
| 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 **) vtr::realloc (netlist_clocks, ++num_netlist_clocks * sizeof(char *)); | |
| netlist_clocks[num_netlist_clocks - 1] = vtr::strdup(name.c_str()); | |
| } | |
| } | |
| //Look for any generated clocks | |
| if(clock_gen_ports.count(model)) { | |
| //This is a clock generator | |
| //Check all the clock generating ports | |
| for(const t_model_ports* model_port : clock_gen_ports[model]) { | |
| AtomPortId clk_gen_port = atom_ctx.nlist.find_atom_port(blk_id, model_port); | |
| for(AtomPinId pin_id : atom_ctx.nlist.port_pins(clk_gen_port)) { | |
| AtomNetId clk_net_id = atom_ctx.nlist.pin_net(pin_id); | |
| VTR_ASSERT(clk_net_id); | |
| std::string name = atom_ctx.nlist.net_name(clk_net_id); | |
| /* Now that we've found a clock, let's see if we've counted it already */ | |
| bool found = false; | |
| for (int i = 0; !found && i < num_netlist_clocks; i++) { | |
| if (netlist_clocks[i] == name) { | |
| 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 **) vtr::realloc (netlist_clocks, ++num_netlist_clocks * sizeof(char *)); | |
| netlist_clocks[num_netlist_clocks - 1] = vtr::strdup(name.c_str()); | |
| } | |
| } | |
| } | |
| } | |
| } else if (type == AtomBlockType::INPAD || type == AtomBlockType::OUTPAD) { | |
| std::string name = atom_ctx.nlist.block_name(blk_id); | |
| /* Now that we've found an I/O, let's see if we've counted it already */ | |
| bool found = false; | |
| for (int i = 0; !found && i < num_netlist_ios; i++) { | |
| if (netlist_ios[i] == name) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| const char* trimmed_name = (type == AtomBlockType::OUTPAD) ? name.c_str() + 4 : name.c_str(); | |
| /* the + 4 removes the prefix "out:" automatically prepended to outputs */ | |
| /* If we get here, the I/O is new and so we dynamically grow the array netlist_ios by one. */ | |
| netlist_ios = (char **) vtr::realloc (netlist_ios, ++num_netlist_ios * sizeof(char *)); | |
| netlist_ios[num_netlist_ios - 1] = vtr::strdup(trimmed_name); | |
| } | |
| } | |
| } | |
| } | |
| static void count_netlist_clocks_as_constrained_clocks() { | |
| /* Counts how many clocks are in the netlist, and adds them to the array timing_ctx.sdc->constrained_clocks. */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| auto& atom_ctx = g_vpr_ctx.atom(); | |
| timing_ctx.sdc->num_constrained_clocks = 0; | |
| for(auto blk_id : atom_ctx.nlist.blocks()) { | |
| //Check for input clocks | |
| for(auto pin_id : atom_ctx.nlist.block_clock_pins(blk_id)) { | |
| AtomNetId clk_net_id = atom_ctx.nlist.pin_net(pin_id); | |
| VTR_ASSERT(clk_net_id); | |
| std::string name = atom_ctx.nlist.net_name(clk_net_id); | |
| add_clock(name); | |
| } | |
| //Check for generated clocks | |
| for(AtomPortId port : atom_ctx.nlist.block_output_ports(blk_id)) { | |
| const t_model_ports* port_model = atom_ctx.nlist.port_model(port); | |
| VTR_ASSERT(port_model->dir == OUT_PORT); | |
| if(port_model->is_clock) { | |
| //This is a clock generator | |
| for(AtomPinId pin : atom_ctx.nlist.port_pins(port)) { | |
| AtomNetId net = atom_ctx.nlist.pin_net(pin); | |
| std::string net_name = atom_ctx.nlist.net_name(net); | |
| add_clock(net_name); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static void add_clock(std::string net_name) { | |
| /* Now that we've found a clock, let's see if we've counted it already */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| bool found = false; | |
| for (int i = 0; !found && i < timing_ctx.sdc->num_constrained_clocks; i++) { | |
| if (timing_ctx.sdc->constrained_clocks[i].name == net_name) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| /* If we get here, the clock is new and so we dynamically grow the array timing_ctx.sdc->constrained_clocks by one. */ | |
| timing_ctx.sdc->constrained_clocks = (t_clock *) vtr::realloc (timing_ctx.sdc->constrained_clocks, ++timing_ctx.sdc->num_constrained_clocks * sizeof(t_clock)); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.sdc->num_constrained_clocks - 1].name = vtr::strdup(net_name.c_str()); | |
| timing_ctx.sdc->constrained_clocks[timing_ctx.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 timing_ctx.sdc->constrained_inputs/ | |
| timing_ctx.sdc->constrained_outputs with an I/O delay of 0 and constrains them to clock clock_name. */ | |
| auto& atom_ctx = g_vpr_ctx.atom(); | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| for(auto blk_id : atom_ctx.nlist.blocks()) { | |
| AtomBlockType type = atom_ctx.nlist.block_type(blk_id); | |
| if (type == AtomBlockType::INPAD) { | |
| std::string name = atom_ctx.nlist.block_name(blk_id); | |
| /* Now that we've found an I/O, let's see if we've counted it already */ | |
| bool found = false; | |
| for (int iinput = 0; !found && iinput < timing_ctx.sdc->num_constrained_inputs; iinput++) { | |
| if (timing_ctx.sdc->constrained_inputs[iinput].name == name) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| /* If we get here, the input is new and so we add it to timing_ctx.sdc->constrained_inputs. */ | |
| timing_ctx.sdc->constrained_inputs = (t_io *) vtr::realloc (timing_ctx.sdc->constrained_inputs, ++timing_ctx.sdc->num_constrained_inputs * sizeof(t_io)); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].name = vtr::strdup(name.c_str()); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].clock_name = vtr::strdup(clock_name); | |
| timing_ctx.sdc->constrained_inputs[timing_ctx.sdc->num_constrained_inputs - 1].delay = io_delay; | |
| } | |
| } else if (type == AtomBlockType::OUTPAD) { | |
| std::string name = atom_ctx.nlist.block_name(blk_id); | |
| /* Now that we've found an I/O, let's see if we've counted it already */ | |
| bool found = false; | |
| for (int ioutput = 0; !found && ioutput < timing_ctx.sdc->num_constrained_outputs; ioutput++) { | |
| if (timing_ctx.sdc->constrained_outputs[ioutput].name == name) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| /* If we get here, the output is new and so we add it to timing_ctx.sdc->constrained_outputs. */ | |
| timing_ctx.sdc->constrained_outputs = (t_io *) vtr::realloc (timing_ctx.sdc->constrained_outputs, ++timing_ctx.sdc->num_constrained_outputs * sizeof(t_io)); | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].name = vtr::strdup(name.c_str() + 4); | |
| /* the + 4 removes the prefix "out:" automatically prepended to outputs */ | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].clock_name = vtr::strdup(clock_name); | |
| timing_ctx.sdc->constrained_outputs[timing_ctx.sdc->num_constrained_outputs - 1].delay = io_delay; | |
| } | |
| } | |
| } | |
| } | |
| static int find_constrained_clock(char * ptr) { | |
| /* Given a string ptr, find whether it's the name of a clock in the array timing_ctx.sdc->constrained_clocks. * | |
| * if it is, return the clock's index in timing_ctx.sdc->constrained_clocks; if it's not, return -1. */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| int index; | |
| for (index = 0; index < timing_ctx.sdc->num_constrained_clocks; index++) { | |
| if (strcmp(ptr, timing_ctx.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 timing_ctx.sdc->cc_constraints; if there is not, return -1. */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| int icc, isource, isink; | |
| for (icc = 0; icc < timing_ctx.sdc->num_cc_constraints; icc++) { | |
| for (isource = 0; isource < timing_ctx.sdc->cc_constraints[icc].num_source; isource++) { | |
| if (strcmp(timing_ctx.sdc->cc_constraints[icc].source_list[isource], source_clock_name) == 0) { | |
| for (isink = 0; isink < timing_ctx.sdc->cc_constraints[icc].num_sink; isink++) { | |
| if (strcmp(timing_ctx.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, bool domain_level_from, bool domain_level_to, | |
| bool 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. */ | |
| auto& timing_ctx = g_vpr_ctx.timing(); | |
| 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 = &timing_ctx.sdc->cc_constraints; | |
| num_constraints = ++timing_ctx.sdc->num_cc_constraints; | |
| } else { /* Clock-to-flipflop constraint */ | |
| constraint_array = &timing_ctx.sdc->cf_constraints; | |
| num_constraints = ++timing_ctx.sdc->num_cf_constraints; | |
| } | |
| } else { | |
| if (domain_level_to) { /* Flipflop-to-clock constraint */ | |
| constraint_array = &timing_ctx.sdc->fc_constraints; | |
| num_constraints = ++timing_ctx.sdc->num_fc_constraints; | |
| } else { /* Flipflop-to-flipflop constraint */ | |
| constraint_array = &timing_ctx.sdc->ff_constraints; | |
| num_constraints = ++timing_ctx.sdc->num_ff_constraints; | |
| } | |
| } | |
| *constraint_array = (t_override_constraint *) vtr::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 **) vtr::malloc(num_from * sizeof(char *)); | |
| (*constraint_array)[num_constraints - 1].sink_list = (char **) vtr::malloc(num_to * sizeof(char *)); | |
| for (i = 0; i < num_from; i++) { | |
| (*constraint_array)[num_constraints - 1].source_list[i] = vtr::strdup(from_list[i]); | |
| } | |
| for (i = 0; i < num_to; i++) { | |
| (*constraint_array)[num_constraints - 1].sink_list[i] = vtr::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 = vtr::get_file_line_number_of_last_opened_file(); /* 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. */ | |
| lcm_period = vtr::lcm(source_period, sink_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 *) vtr::malloc((num_source_edges + 1) * sizeof(int)); | |
| sink_edges = (int *) vtr::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 = 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 bool regex_match (const char * string, const 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; | |
| VTR_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_throw(VPR_ERROR_SDC, sdc_file_name.c_str(), vtr::get_file_line_number_of_last_opened_file(), | |
| "Error matching regular expression \"%s\".\n", regular_expression); | |
| return false; | |
| } | |
| } | |
| void free_sdc_related_structs() { | |
| auto& timing_ctx = g_vpr_ctx.mutable_timing(); | |
| if (!timing_ctx.sdc) return; | |
| free_override_constraint(timing_ctx.sdc->cc_constraints, timing_ctx.sdc->num_cc_constraints); | |
| /* Should already have been freed in process_constraints() */ | |
| free_override_constraint(timing_ctx.sdc->cf_constraints, timing_ctx.sdc->num_cf_constraints); | |
| free_override_constraint(timing_ctx.sdc->fc_constraints, timing_ctx.sdc->num_fc_constraints); | |
| free_override_constraint(timing_ctx.sdc->ff_constraints, timing_ctx.sdc->num_ff_constraints); | |
| free_io_constraint(timing_ctx.sdc->constrained_inputs, timing_ctx.sdc->num_constrained_inputs); | |
| free_io_constraint(timing_ctx.sdc->constrained_outputs, timing_ctx.sdc->num_constrained_outputs); | |
| free_clock_constraint(timing_ctx.sdc->constrained_clocks, timing_ctx.sdc->num_constrained_clocks); | |
| free(timing_ctx.sdc); | |
| timing_ctx.sdc = nullptr; | |
| } | |
| 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] = nullptr; | |
| } | |
| for (j = 0; j < constraint_array[i].num_sink; j++) { | |
| free(constraint_array[i].sink_list[j]); | |
| constraint_array[i].sink_list[j] = nullptr; | |
| } | |
| free(constraint_array[i].source_list); | |
| free(constraint_array[i].sink_list); | |
| } | |
| free(constraint_array); | |
| constraint_array = nullptr; | |
| } | |
| 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 = nullptr; | |
| } | |
| 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 = nullptr; | |
| } | |
| const char * get_sdc_file_name(){ | |
| return sdc_file_name.c_str(); | |
| } | |