blob: 9889723706025067b18bd5051807a3f49190eaed [file] [log] [blame]
#include "read_sdc.h"
#include <regex>
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vtr_util.h"
#include "vtr_math.h"
#include "tatum/TimingGraph.hpp"
#include "tatum/TimingConstraints.hpp"
#include "sdcparse.hpp"
#include "vpr_error.h"
#include "atom_netlist.h"
#include "atom_netlist_utils.h"
#include "atom_lookup.h"
void apply_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& timing_constraints);
void apply_combinational_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& timing_constraints);
void apply_single_clock_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
const AtomPinId clock_driver,
tatum::TimingConstraints& timing_constraints);
void apply_multi_clock_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
const std::set<AtomPinId>& clock_drivers,
tatum::TimingConstraints& timing_constraints);
void mark_constant_generators(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc);
void constrain_all_ios(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc,
tatum::DomainId input_domain,
tatum::DomainId output_domain,
tatum::Time input_delay,
tatum::Time output_delay);
std::map<std::string, AtomPinId> find_netlist_primary_ios(const AtomNetlist& netlist);
std::string orig_blif_name(std::string name);
std::regex glob_pattern_to_regex(const std::string& glob_pattern);
class SdcParseCallback : public sdcparse::Callback {
public:
SdcParseCallback(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& timing_constraints,
tatum::TimingGraph& tg)
: netlist_(netlist)
, lookup_(lookup)
, tc_(timing_constraints)
, tg_(tg) {}
public: //sdcparse::Callback interface
//Start of parsing
void start_parse() override {
netlist_clock_drivers_ = find_netlist_logical_clock_drivers(netlist_);
netlist_primary_ios_ = find_netlist_primary_ios(netlist_);
}
//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 {
++num_commands_;
if (cmd.is_virtual) {
//Create a virtual clock
tatum::DomainId virtual_clk = tc_.create_clock_domain(cmd.name);
if (sdc_clocks_.count(virtual_clk)) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Found duplicate virtual clock definition for clock '%s'",
cmd.name.c_str());
}
//Virtual clocks should have no targets
if (!cmd.targets.strings.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Virtual clock definition (i.e. with '-name') should not have targets");
}
//Save the mapping to the sdc clock info
sdc_clocks_[virtual_clk] = cmd;
} else {
//Create a netlist clock for every matching netlist clock
for (const std::string& clock_name_glob_pattern : cmd.targets.strings) {
bool found = false;
//We interpret each SDC target as glob-style pattern matches, which we
//convert to a regex
auto clock_name_regex = glob_pattern_to_regex(clock_name_glob_pattern);
//Look for matching netlist clocks
for (AtomPinId clock_pin : netlist_clock_drivers_) {
AtomNetId clock_net = netlist_.pin_net(clock_pin);
const auto& clock_name = netlist_.net_name(clock_net);
if (std::regex_match(clock_name, clock_name_regex)) {
found = true;
//Create netlist clock
tatum::DomainId netlist_clk = tc_.create_clock_domain(clock_name);
if (sdc_clocks_.count(netlist_clk)) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Found duplicate netlist clock definition for clock '%s' matching target pattern '%s'",
clock_name.c_str(), clock_name_glob_pattern.c_str());
}
//Set the clock source
AtomPinId clock_driver = netlist_.net_driver(clock_net);
tatum::NodeId clock_source = lookup_.atom_pin_tnode(clock_driver);
VTR_ASSERT(clock_source);
tc_.set_clock_domain_source(clock_source, netlist_clk);
//Save the mapping to the clock info
sdc_clocks_[netlist_clk] = cmd;
}
}
if (!found) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Clock name or pattern '%s' does not correspond to any nets."
" To create a virtual clock, use the '-name' option.",
clock_name_glob_pattern.c_str());
}
}
}
}
void set_io_delay(const sdcparse::SetIoDelay& cmd) override {
++num_commands_;
tatum::DomainId domain;
if (cmd.clock_name == "*") {
if (netlist_clock_drivers_.size() == 1) {
//Support non-standard wildcard clock name for set_input_delay/set_output_delay
//commands, provided it is unambiguous (i.e. there is only one netlist clock)
AtomNetId clock_net = netlist_.pin_net(*netlist_clock_drivers_.begin());
std::string clock_name = netlist_.net_name(clock_net);
domain = tc_.find_clock_domain(clock_name);
} else {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Wildcard clock domain '%s' is ambiguous in multi-clock circuits, explicitly specify the target clock",
cmd.clock_name.c_str());
}
} else {
//Regular look-up
domain = tc_.find_clock_domain(cmd.clock_name);
}
//Error checks
if (!domain) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Failed to find clock domain '%s' for I/O constraint",
cmd.clock_name.c_str());
}
//Find all matching I/Os
auto io_pins = get_ports(cmd.target_ports);
if (io_pins.empty()) {
//We treat this as a warning, since the primary I/Os in the target may have been swept away
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"Found no matching primary inputs or primary outputs for %s\n",
(cmd.type == sdcparse::IoDelayType::INPUT) ? "set_input_delay" : "set_output_delay");
}
bool is_max = cmd.is_max;
bool is_min = cmd.is_min;
if (!is_max && !is_min) {
//Unspecified implies both
is_max = true;
is_min = true;
}
float delay = sdc_units_to_seconds(cmd.delay);
for (AtomPinId pin : io_pins) {
tatum::NodeId tnode = lookup_.atom_pin_tnode(pin);
VTR_ASSERT(tnode);
//Set i/o constraint
if (cmd.type == sdcparse::IoDelayType::INPUT) {
if (netlist_.pin_type(pin) == PinType::DRIVER) {
if (is_max) {
tc_.set_input_constraint(tnode, domain, tatum::DelayType::MAX, tatum::Time(delay));
}
if (is_min) {
tc_.set_input_constraint(tnode, domain, tatum::DelayType::MIN, tatum::Time(delay));
}
} else {
VTR_ASSERT(netlist_.pin_type(pin) == PinType::SINK);
AtomBlockId blk = netlist_.pin_block(pin);
std::string io_name = orig_blif_name(netlist_.block_name(blk));
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"set_input_delay command matched but was not applied to primary output '%s'\n",
io_name.c_str());
}
} else {
VTR_ASSERT(cmd.type == sdcparse::IoDelayType::OUTPUT);
if (netlist_.pin_type(pin) == PinType::SINK) {
if (is_max) {
tc_.set_output_constraint(tnode, domain, tatum::DelayType::MAX, tatum::Time(delay));
}
if (is_min) {
tc_.set_output_constraint(tnode, domain, tatum::DelayType::MIN, tatum::Time(delay));
}
} else {
VTR_ASSERT(netlist_.pin_type(pin) == PinType::DRIVER);
AtomBlockId blk = netlist_.pin_block(pin);
std::string io_name = orig_blif_name(netlist_.block_name(blk));
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"set_output_delay command matched but was not applied to primary input '%s'\n",
io_name.c_str());
}
}
}
}
void set_clock_groups(const sdcparse::SetClockGroups& cmd) override {
++num_commands_;
if (cmd.type != sdcparse::ClockGroupsType::EXCLUSIVE) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_clock_groups only supports -exclusive groups");
}
//FIXME: more efficient to collect per-group clocks once instead of at each iteration
//Disable timing between each group of clock domains
for (size_t src_group = 0; src_group < cmd.clock_groups.size(); ++src_group) {
auto src_clocks = get_clocks(cmd.clock_groups[src_group]);
for (size_t sink_group = 0; sink_group < cmd.clock_groups.size(); ++sink_group) {
if (src_group == sink_group) continue;
auto sink_clocks = get_clocks(cmd.clock_groups[sink_group]);
for (auto src_clock : src_clocks) {
for (auto sink_clock : sink_clocks) {
//Mark this pair of domains to be disabled
disabled_domain_pairs_.insert({src_clock, sink_clock});
}
}
}
}
}
void set_false_path(const sdcparse::SetFalsePath& cmd) override {
++num_commands_;
auto from_clocks = get_clocks(cmd.from);
auto to_clocks = get_clocks(cmd.to);
if (from_clocks.empty() && to_clocks.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_false_path must specify at least one -from or -to clock");
}
if (from_clocks.empty()) {
from_clocks = get_all_clocks();
}
if (to_clocks.empty()) {
to_clocks = get_all_clocks();
}
for (auto from_clock : from_clocks) {
for (auto to_clock : to_clocks) {
//Mark this domain pair to be disabled
disabled_domain_pairs_.insert({from_clock, to_clock});
}
}
}
void set_min_max_delay(const sdcparse::SetMinMaxDelay& cmd) override {
++num_commands_;
auto from_clocks = get_clocks(cmd.from);
auto to_clocks = get_clocks(cmd.to);
if (from_clocks.empty() && to_clocks.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_max_path must specify at least one -from or -to clock");
}
if (from_clocks.empty()) {
from_clocks = get_all_clocks();
}
if (to_clocks.empty()) {
to_clocks = get_all_clocks();
}
float constraint = cmd.value;
for (auto from_clock : from_clocks) {
for (auto to_clock : to_clocks) {
//Mark this domain pair to be disabled
auto key = std::make_pair(from_clock, to_clock);
if (cmd.type == sdcparse::MinMaxType::MAX) {
setup_override_constraints_[key] = constraint;
} else {
VTR_ASSERT(cmd.type == sdcparse::MinMaxType::MIN);
hold_override_constraints_[key] = constraint;
}
}
}
}
void set_multicycle_path(const sdcparse::SetMulticyclePath& cmd) override {
++num_commands_;
std::set<tatum::DomainId> from_clocks;
std::set<tatum::DomainId> to_clocks;
std::set<AtomPinId> to_pins;
if (cmd.from.type == sdcparse::StringGroupType::CLOCK
|| cmd.from.type == sdcparse::StringGroupType::STRING) {
//Treat raw strings (i.e. no get_clocks) as clocks
from_clocks = get_clocks(cmd.from);
} else {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_multicycle_path only supports specifying clocks for -from");
}
if (cmd.to.type == sdcparse::StringGroupType::CLOCK
|| cmd.to.type == sdcparse::StringGroupType::STRING) {
//Treat raw strings (i.e. no get_clocks) as clocks
to_clocks = get_clocks(cmd.to);
} else if (cmd.to.type == sdcparse::StringGroupType::PIN) {
to_pins = get_pins(cmd.to);
if (to_pins.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_multicycle_path requires non-empty pin set for -to [get_pins ...]");
}
} else {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"set_multicycle_path only supports specifying clocks or pins for -to");
}
if (from_clocks.empty()) {
from_clocks = get_all_clocks();
}
if (to_clocks.empty()) {
to_clocks = get_all_clocks();
}
if (to_pins.empty()) {
//Treat INVALID pin as wildcard
to_pins = {AtomPinId::INVALID()};
}
int setup_mcp = cmd.mcp_value;
int hold_mcp = cmd.mcp_value;
bool is_setup = cmd.is_setup;
bool is_hold = cmd.is_hold || is_setup; //Specifying a setup mcp also modifies hold
if (!is_hold && !is_setup) {
//Unspecified implicitly sets the setup mcp to the target value,
//and the hold mcp to zero
is_setup = true;
is_hold = true;
VTR_ASSERT(setup_mcp == cmd.mcp_value);
hold_mcp = 0; //Default hold check is 0
}
for (auto from_clock : from_clocks) {
for (auto to_clock : to_clocks) {
for (auto to_pin : to_pins) {
auto node_domain = std::make_tuple(from_clock, to_clock, to_pin);
if (is_setup) {
setup_mcp_overrides_[node_domain] = setup_mcp;
}
if (is_hold) {
hold_mcp_overrides_[node_domain] = hold_mcp;
}
}
}
}
}
void set_clock_uncertainty(const sdcparse::SetClockUncertainty& cmd) override {
++num_commands_;
auto from_clocks = get_clocks(cmd.from);
auto to_clocks = get_clocks(cmd.to);
if (from_clocks.empty()) {
from_clocks = get_all_clocks();
}
if (to_clocks.empty()) {
to_clocks = get_all_clocks();
}
float uncertainty = sdc_units_to_seconds(cmd.value);
bool is_setup = cmd.is_setup;
bool is_hold = cmd.is_hold;
if (!is_hold && !is_setup) {
//Unspecified is implicitly both setup and hold
is_setup = true;
is_hold = true;
}
for (auto from_clock : from_clocks) {
for (auto to_clock : to_clocks) {
if (is_setup) {
tc_.set_setup_clock_uncertainty(from_clock, to_clock, tatum::Time(uncertainty));
}
if (is_hold) {
tc_.set_hold_clock_uncertainty(from_clock, to_clock, tatum::Time(uncertainty));
}
}
}
}
void set_clock_latency(const sdcparse::SetClockLatency& cmd) override {
++num_commands_;
if (cmd.type != sdcparse::ClockLatencyType::SOURCE) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_, "set_clock_latency only supports specifying -source latency");
}
auto clocks = get_clocks(cmd.target_clocks);
if (clocks.empty()) {
clocks = get_all_clocks();
}
bool is_early = cmd.is_early;
bool is_late = cmd.is_late;
if (!is_early && !is_late) {
//Unspecified is implicitly both early and late
is_early = true;
is_late = true;
}
float latency = sdc_units_to_seconds(cmd.value);
for (auto clock : clocks) {
if (is_early) {
tc_.set_source_latency(clock, tatum::ArrivalType::EARLY, tatum::Time(latency));
}
if (is_late) {
tc_.set_source_latency(clock, tatum::ArrivalType::LATE, tatum::Time(latency));
}
}
}
void set_disable_timing(const sdcparse::SetDisableTiming& cmd) override {
++num_commands_;
//Collect the specified pins
auto from_pins = get_pins(cmd.from);
auto to_pins = get_pins(cmd.to);
if (from_pins.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Found no matching -from pins");
}
if (to_pins.empty()) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Found no matching -to pins");
}
//Disable any edges between the from and to sets
for (auto from_pin : from_pins) {
for (auto to_pin : to_pins) {
tatum::NodeId from_tnode = lookup_.atom_pin_tnode(from_pin);
VTR_ASSERT(from_tnode);
tatum::NodeId to_tnode = lookup_.atom_pin_tnode(to_pin);
VTR_ASSERT(to_tnode);
//Find the edge matching these nodes
tatum::EdgeId edge = tg_.find_edge(from_tnode, to_tnode);
if (!edge) {
const auto& from_pin_name = netlist_.pin_name(from_pin);
const auto& to_pin_name = netlist_.pin_name(to_pin);
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"set_disable_timing no timing edge found from pin '%s' to pin '%s'\n",
from_pin_name.c_str(), to_pin_name.c_str());
}
//Mark the edge in the timing graph as disabled
tg_.disable_edge(edge);
//If we have disabled all incoming edges of the to_tnode we need to mark
//it as a constant generator to avoid causing errors during timing analysis
//(since the node will appear in the first level of the timing graph but is not
//a primary input).
if (tg_.node_num_active_in_edges(to_tnode) == 0) {
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"set_disable_timing caused pin '%s' to have no active incoming edges. It is being marked as a constant generator.\n",
netlist_.pin_name(to_pin).c_str());
tc_.set_constant_generator(to_tnode);
}
}
}
}
void set_timing_derate(const sdcparse::SetTimingDerate& /*cmd*/) override {
++num_commands_;
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_, "set_timing_derate currently unsupported");
}
//End of parsing
void finish_parse() override {
//Mark constant generator timing nodes
mark_constant_generators(netlist_, lookup_, tc_);
//Determine the final clock constraints
resolve_clock_constraints();
//Re-levelize if needed (e.g. due to set_disable_timing)
tg_.levelize();
}
//Error during parsing
void parse_error(const int /*curr_lineno*/, const std::string& near_text, const std::string& msg) override {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_, "%s near '%s'", msg.c_str(), near_text.c_str());
}
public:
size_t num_commands() { return num_commands_; }
private:
void resolve_clock_constraints() {
//Set the clock constraints
for (tatum::DomainId launch_clock : tc_.clock_domains()) {
for (tatum::DomainId capture_clock : tc_.clock_domains()) {
auto domain_pair = std::make_pair(launch_clock, capture_clock);
if (disabled_domain_pairs_.count(domain_pair)) continue;
//Setup -- default
{
tatum::Time setup_constraint = calculate_setup_constraint(launch_clock, capture_clock);
VTR_ASSERT(setup_constraint.valid());
tc_.set_setup_constraint(launch_clock, capture_clock, setup_constraint);
}
//Setup -- capture pin overrides
for (auto pin : setup_capture_pins_with_overrides()) {
tatum::Time setup_constraint = calculate_setup_constraint(launch_clock, capture_clock, pin);
VTR_ASSERT(setup_constraint.valid());
tatum::NodeId tnode = lookup_.atom_pin_tnode(pin);
VTR_ASSERT(tnode);
tc_.set_setup_constraint(launch_clock, capture_clock, tnode, setup_constraint);
}
//Hold -- default
{
tatum::Time hold_constraint = calculate_hold_constraint(launch_clock, capture_clock);
VTR_ASSERT(hold_constraint.valid());
tc_.set_hold_constraint(launch_clock, capture_clock, hold_constraint);
}
//Setup -- capture pin overrides
for (auto pin : hold_capture_pins_with_overrides()) {
tatum::Time hold_constraint = calculate_hold_constraint(launch_clock, capture_clock, pin);
VTR_ASSERT(hold_constraint.valid());
tatum::NodeId tnode = lookup_.atom_pin_tnode(pin);
VTR_ASSERT(tnode);
tc_.set_hold_constraint(launch_clock, capture_clock, tnode, hold_constraint);
}
}
}
}
//Returns the setup constraint in seconds
tatum::Time calculate_setup_constraint(tatum::DomainId launch_domain, tatum::DomainId capture_domain, AtomPinId to_pin = AtomPinId::INVALID()) const {
//Calculate the period-based constraint, including the effect of multi-cycle paths
float min_launch_to_capture_time = calculate_min_launch_to_capture_edge_time(launch_domain, capture_domain);
auto iter = sdc_clocks_.find(capture_domain);
VTR_ASSERT(iter != sdc_clocks_.end());
float capture_period = iter->second.period;
//The period based constraint is the minimum launch to capture edge time + the capture period * (extra_cycles)
//
// Since min_launch_to_capture_time is the minimum time to the first capture edge after a launch edge, it already
// implicitly includes one capture cycle. As a result we subtract 1 from the setup capture cycle value to determine
// how many extra capture cycles need to be added to the constraint.
//
// By default the setup capture cycle 1, specifying a capture 1 cycle after launch
int extra_cycles = setup_capture_cycle(launch_domain, capture_domain, to_pin) - 1;
float period_based_setup_constraint = min_launch_to_capture_time + capture_period * extra_cycles;
//Warn the user if we added negative cycles
if (extra_cycles < 0) {
VTR_LOG_WARN(
"Added negative (%d) additional capture clock cycles to setup constraint"
" for clock '%s' to clock '%s' transfers; check your set_multicycle_path specifications\n",
extra_cycles, tc_.clock_domain_name(launch_domain).c_str(), tc_.clock_domain_name(capture_domain).c_str());
}
//By default the period-based constraint is the constraint
float setup_constraint = period_based_setup_constraint;
//See if we have any other override constraints
auto domain_pair = std::make_pair(launch_domain, capture_domain);
auto override_iter = setup_override_constraints_.find(domain_pair);
if (override_iter != setup_override_constraints_.end()) {
float override_setup_constraint = override_iter->second;
if (setup_constraint > override_setup_constraint) {
VTR_LOG_WARN(
"Override setup constraint (%g) overrides a tighter default period-based constraint (%g)"
" for transfers from clock '%s' to clock '%s'\n",
override_setup_constraint, setup_constraint,
tc_.clock_domain_name(launch_domain).c_str(),
tc_.clock_domain_name(capture_domain).c_str());
}
//Override the constarint
setup_constraint = override_setup_constraint;
}
setup_constraint = sdc_units_to_seconds(setup_constraint);
if (setup_constraint < 0.) {
VPR_ERROR(VPR_ERROR_SDC,
"Setup constraint %g for transfers from clock '%s' to clock '%s' is negative."
" Requires data to arrive before launch edge (No time travelling allowed!)",
setup_constraint,
tc_.clock_domain_name(launch_domain).c_str(),
tc_.clock_domain_name(capture_domain).c_str());
}
return tatum::Time(setup_constraint);
}
//Returns the hold constraint in seconds
tatum::Time calculate_hold_constraint(tatum::DomainId launch_domain, tatum::DomainId capture_domain, AtomPinId to_pin = AtomPinId::INVALID()) const {
float min_launch_to_capture_time = calculate_min_launch_to_capture_edge_time(launch_domain, capture_domain);
auto iter = sdc_clocks_.find(capture_domain);
VTR_ASSERT(iter != sdc_clocks_.end());
float capture_period = iter->second.period;
//The period based constraint is the minimum launch to capture edge time + the capture period * extra_cycles
//
// Since min_launch_to_capture_time is the minimum time to the first capture edge *after* a launch edge, it already
// implicitly includes one capture cycle. As a result we subtract 1 from the hold capture cycle value to determine
// how many extra capture cycles need to be added to the constraint.
//
// For the default hold check is one cycle before the setup check
// For the default setup check (1) this means extra_cycles is -1 (i.e. the hold capture check occurs against
// the capture edge *before* the launch edge)
int extra_cycles = hold_capture_cycle(launch_domain, capture_domain, to_pin) - 1;
float period_based_hold_constraint = min_launch_to_capture_time + capture_period * extra_cycles;
//By default the period-based constraint is the constraint
float hold_constraint = period_based_hold_constraint;
//See if we have any other override constraints
auto domain_pair = std::make_pair(launch_domain, capture_domain);
auto override_iter = hold_override_constraints_.find(domain_pair);
if (override_iter != hold_override_constraints_.end()) {
float override_hold_constraint = override_iter->second;
if (hold_constraint < override_hold_constraint) {
VTR_LOG_WARN(
"Override hold constraint (%g) overrides tighter default period-based constraint (%g)"
" for transfers from clock '%s' to clock '%s'\n",
override_hold_constraint, hold_constraint,
tc_.clock_domain_name(launch_domain).c_str(),
tc_.clock_domain_name(capture_domain).c_str());
}
//Override the constarint
hold_constraint = override_hold_constraint;
}
hold_constraint = sdc_units_to_seconds(hold_constraint);
return tatum::Time(hold_constraint);
}
//Determine the minumum time (in SDC units) between the edges of the launch and capture clocks
float calculate_min_launch_to_capture_edge_time(tatum::DomainId launch_domain, tatum::DomainId capture_domain) const {
constexpr int CLOCK_SCALE = 1000;
auto launch_iter = sdc_clocks_.find(launch_domain);
VTR_ASSERT(launch_iter != sdc_clocks_.end());
const sdcparse::CreateClock& launch_clock = launch_iter->second;
auto capture_iter = sdc_clocks_.find(capture_domain);
VTR_ASSERT(capture_iter != sdc_clocks_.end());
const sdcparse::CreateClock& capture_clock = capture_iter->second;
VTR_ASSERT_MSG(launch_clock.period >= 0., "Clock period must be positive");
VTR_ASSERT_MSG(capture_clock.period >= 0., "Clock period must be positive");
float constraint = std::numeric_limits<float>::quiet_NaN();
if (std::fabs(launch_clock.period - capture_clock.period) < EPSILON && std::fabs(launch_clock.rise_edge - capture_clock.rise_edge) < EPSILON && std::fabs(launch_clock.fall_edge - capture_clock.fall_edge) < EPSILON) {
//The source and sink domains have the same period and edges, the constraint is the common clock period.
constraint = launch_clock.period;
} else if (launch_clock.period < EPSILON || capture_clock.period < EPSILON) {
//If either period is 0, the constraint is 0
constraint = 0.;
} else {
/*
* Use edge counting to find the minimum launch to capture edge time
*/
//Multiply periods and edges by CLOCK_SCALE and round down to the nearest
//integer, to avoid messy decimals.
int launch_period = static_cast<int>(launch_clock.period * CLOCK_SCALE);
int capture_period = static_cast<int>(capture_clock.period * CLOCK_SCALE);
int launch_rise_edge = static_cast<int>(launch_clock.rise_edge * CLOCK_SCALE);
int capture_rise_edge = static_cast<int>(capture_clock.rise_edge * CLOCK_SCALE);
//Find the LCM of the two periods. This determines how long it takes before
//the pattern of the two clock's edges starts repeating.
int lcm_period = vtr::lcm(launch_period, capture_period);
//Create arrays of positive edges for each clock over one LCM clock period.
//Launch edges
int launch_rise_time = launch_rise_edge;
std::vector<int> launch_edges;
int num_launch_edges = lcm_period / launch_period + 1;
for (int i = 0; i < num_launch_edges; ++i) {
launch_edges.push_back(launch_rise_time);
launch_rise_time += launch_period;
}
//Capture edges
int capture_rise_time = capture_rise_edge;
int num_capture_edges = lcm_period / capture_period + 1;
std::vector<int> capture_edges;
for (int i = 0; i < num_capture_edges; ++i) {
capture_edges.push_back(capture_rise_time);
capture_rise_time += capture_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 yeilds the setup time constraint.
int scaled_constraint = std::numeric_limits<int>::max(); //Initialize to +inf, so any constraint will be less
for (int launch_edge : launch_edges) {
for (int capture_edge : capture_edges) {
if (capture_edge >= launch_edge) { //Postive only
int edge_diff = capture_edge - launch_edge;
VTR_ASSERT(edge_diff >= 0.);
scaled_constraint = std::min(scaled_constraint, edge_diff);
}
}
}
//Rescale the constraint back to a float
constraint = float(scaled_constraint) / CLOCK_SCALE;
}
return constraint;
}
//Returns the cycle number (after launch) where the setup check occurs
int setup_capture_cycle(tatum::DomainId from, tatum::DomainId to, AtomPinId to_pin = AtomPinId::INVALID()) const {
//The setup capture cycle is the setup mcp value
//Any domain pair + pin-specific (possibly wildcard) override
auto key = std::make_tuple(from, to, to_pin);
auto iter = setup_mcp_overrides_.find(key);
if (iter != setup_mcp_overrides_.end()) {
return iter->second;
}
//Pin-specific override not found, look for Domain pair overrides
key = std::make_tuple(from, to, AtomPinId::INVALID());
iter = setup_mcp_overrides_.find(key);
if (iter != setup_mcp_overrides_.end()) {
return iter->second;
}
//Default: capture one cycle after launch
return 1;
}
//Returns the cycle number (after launch) where the hold check occurs
int hold_capture_cycle(tatum::DomainId from, tatum::DomainId to, AtomPinId to_pin = AtomPinId::INVALID()) const {
//Default: hold captures the cycle before setup is captured
//For the default setup mcp this implies capturing the same
//cycle as launch
int hold_offset = 1;
//Any domain pair + pin-specific (possibly wildcard) override
auto key = std::make_tuple(from, to, to_pin);
auto iter = hold_mcp_overrides_.find(key);
if (iter != hold_mcp_overrides_.end()) {
//Note that we add the override to the default hold_mcp of 1 to match
//the standard SDC behaviour (e.g. N - 1) of hold multicycles.
//
//For details see section 8.3 'Multicycle paths' in:
// J. Bhasker, R. Chadha, "Static Timing Analysis for Nanometer
// Designs A Practical Approach", Springer, 2009
hold_offset += iter->second;
} else {
//Pin-specific override not found, look for Domain pair overrides
key = std::make_tuple(from, to, AtomPinId::INVALID());
iter = hold_mcp_overrides_.find(key);
if (iter != hold_mcp_overrides_.end()) {
hold_offset += iter->second;
}
}
//The hold capture cycle is the setup capture cycle minus the hold mcp value
return setup_capture_cycle(from, to, to_pin) - hold_offset;
}
std::set<AtomPinId> get_ports(const sdcparse::StringGroup& port_group) {
if (port_group.type != sdcparse::StringGroupType::PORT) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Expected port collection via get_ports");
}
std::set<AtomPinId> pins;
for (const auto& port_pattern : port_group.strings) {
std::regex port_regex = glob_pattern_to_regex(port_pattern);
bool found = false;
for (const auto& kv : netlist_primary_ios_) {
const std::string& io_name = kv.first;
if (std::regex_match(io_name, port_regex)) {
found = true;
AtomPinId pin = kv.second;
pins.insert(pin);
}
}
if (!found) {
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"get_ports target name or pattern '%s' matched no ports\n",
port_pattern.c_str());
}
}
return pins;
}
std::set<tatum::DomainId> get_clocks(const sdcparse::StringGroup& clock_group) {
std::set<tatum::DomainId> domains;
if (clock_group.strings.empty()) {
return domains;
}
if (clock_group.type != sdcparse::StringGroupType::CLOCK
&& clock_group.type != sdcparse::StringGroupType::STRING) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Expected clock names or collection via get_clocks");
}
for (const auto& clock_glob_pattern : clock_group.strings) {
std::regex clock_regex = glob_pattern_to_regex(clock_glob_pattern);
bool found = false;
for (tatum::DomainId domain : tc_.clock_domains()) {
const std::string& clock_name = tc_.clock_domain_name(domain);
if (std::regex_match(clock_name, clock_regex)) {
found = true;
domains.insert(domain);
}
}
if (!found) {
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"get_clocks target name or pattern '%s' matched no clocks\n",
clock_glob_pattern.c_str());
}
}
return domains;
}
std::set<AtomPinId> get_pins(const sdcparse::StringGroup& pin_group) {
std::set<AtomPinId> pins;
if (pin_group.strings.empty()) {
return pins;
}
if (pin_group.type != sdcparse::StringGroupType::PIN) {
vpr_throw(VPR_ERROR_SDC, fname_.c_str(), lineno_,
"Expected pin collection via get_pins");
}
for (const auto& pin_pattern : pin_group.strings) {
std::regex pin_regex = glob_pattern_to_regex(pin_pattern);
bool found = false;
for (AtomPinId pin : netlist_.pins()) {
const std::string& pin_name = netlist_.pin_name(pin);
if (std::regex_match(pin_name, pin_regex)) {
found = true;
pins.insert(pin);
}
}
if (!found) {
VTR_LOGF_WARN(fname_.c_str(), lineno_,
"get_pins target name or pattern '%s' matched no pins\n",
pin_pattern.c_str());
}
}
return pins;
}
std::set<tatum::DomainId> get_all_clocks() {
auto domains = tc_.clock_domains();
return std::set<tatum::DomainId>(domains.begin(), domains.end());
}
float sdc_units_to_seconds(float val) const {
return val * unit_scale_;
}
float seconds_to_sdc_units(float val) const {
return val / unit_scale_;
}
std::set<AtomPinId> setup_capture_pins_with_overrides() {
std::set<AtomPinId> pins;
for (auto kv : setup_mcp_overrides_) {
pins.insert(std::get<2>(kv.first));
}
//We use the invalid pin as a placehold for the default constraint,
//so it should not be included in the set of pins with overrides.
pins.erase(AtomPinId::INVALID());
return pins;
}
std::set<AtomPinId> hold_capture_pins_with_overrides() {
std::set<AtomPinId> pins;
for (auto kv : hold_mcp_overrides_) {
pins.insert(std::get<2>(kv.first));
}
//We use the invalid pin as a placehold for the default constraint,
//so it should not be included in the set of pins with overrides.
pins.erase(AtomPinId::INVALID());
return pins;
}
private:
const AtomNetlist& netlist_;
const AtomLookup& lookup_;
tatum::TimingConstraints& tc_;
tatum::TimingGraph& tg_;
size_t num_commands_ = 0;
std::string fname_;
int lineno_ = -1;
float unit_scale_ = 1e-9;
std::map<tatum::DomainId, sdcparse::CreateClock> sdc_clocks_;
std::set<AtomPinId> netlist_clock_drivers_;
std::map<std::string, AtomPinId> netlist_primary_ios_;
std::set<std::pair<tatum::DomainId, tatum::DomainId>> disabled_domain_pairs_;
std::map<std::pair<tatum::DomainId, tatum::DomainId>, float> setup_override_constraints_;
std::map<std::pair<tatum::DomainId, tatum::DomainId>, float> hold_override_constraints_;
std::map<std::tuple<tatum::DomainId, tatum::DomainId, AtomPinId>, int> setup_mcp_overrides_;
std::map<std::tuple<tatum::DomainId, tatum::DomainId, AtomPinId>, int> hold_mcp_overrides_;
};
std::unique_ptr<tatum::TimingConstraints> read_sdc(const t_timing_inf& timing_inf,
const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingGraph& timing_graph) {
auto timing_constraints = std::make_unique<tatum::TimingConstraints>();
if (!timing_inf.timing_analysis_enabled) {
VTR_LOG("\n");
VTR_LOG("Timing analysis off\n");
apply_default_timing_constraints(netlist, lookup, *timing_constraints);
} else {
FILE* sdc_file = fopen(timing_inf.SDCFile.c_str(), "r");
if (sdc_file == nullptr) {
//No SDC file
VTR_LOG("\n");
VTR_LOG("SDC file '%s' not found\n", timing_inf.SDCFile.c_str());
apply_default_timing_constraints(netlist, lookup, *timing_constraints);
} else {
VTR_ASSERT(sdc_file != nullptr);
//Parse the file
SdcParseCallback callback(netlist, lookup, *timing_constraints, timing_graph);
sdc_parse_file(sdc_file, callback, timing_inf.SDCFile.c_str());
fclose(sdc_file);
if (callback.num_commands() == 0) {
VTR_LOG("\n");
VTR_LOG("SDC file '%s' contained no SDC commands\n", timing_inf.SDCFile.c_str());
apply_default_timing_constraints(netlist, lookup, *timing_constraints);
} else {
VTR_LOG("\n");
VTR_LOG("Applied %zu SDC commands from '%s'\n", callback.num_commands(), timing_inf.SDCFile.c_str());
}
}
}
VTR_LOG("Timing constraints created %zu clocks\n", timing_constraints->clock_domains().size());
for (tatum::DomainId domain : timing_constraints->clock_domains()) {
if (timing_constraints->is_virtual_clock(domain)) {
VTR_LOG(" Constrained Clock '%s' (Virtual Clock)\n",
timing_constraints->clock_domain_name(domain).c_str());
} else {
tatum::NodeId src_tnode = timing_constraints->clock_domain_source_node(domain);
VTR_ASSERT(src_tnode);
AtomPinId src_pin = lookup.tnode_atom_pin(src_tnode);
VTR_ASSERT(src_pin);
VTR_LOG(" Constrained Clock '%s' Source: '%s'\n",
timing_constraints->clock_domain_name(domain).c_str(),
netlist.pin_name(src_pin).c_str());
}
}
VTR_LOG("\n");
return timing_constraints;
}
//Apply the default timing constraints (i.e. if there are no user specified constraints)
//appropriate to the type of circuit.
void apply_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc) {
std::set<AtomPinId> netlist_clock_drivers = find_netlist_logical_clock_drivers(netlist);
if (netlist_clock_drivers.size() == 0) {
apply_combinational_default_timing_constraints(netlist, lookup, tc);
} else if (netlist_clock_drivers.size() == 1) {
apply_single_clock_default_timing_constraints(netlist, lookup, *netlist_clock_drivers.begin(), tc);
} else {
VTR_ASSERT(netlist_clock_drivers.size() > 1);
apply_multi_clock_default_timing_constraints(netlist, lookup, netlist_clock_drivers, tc);
}
}
//Apply the default timing constraints for purely combinational circuits which have
//no explicit netlist clock
void apply_combinational_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc) {
std::string clock_name = "virtual_io_clock";
VTR_LOG("Setting default timing constraints:\n");
VTR_LOG(" * constrain all primay inputs and primary outputs on a virtual external clock '%s'\n", clock_name.c_str());
VTR_LOG(" * optimize virtual clock to run as fast as possible\n");
//Create a virtual clock, with 0 period
tatum::DomainId domain = tc.create_clock_domain(clock_name);
tc.set_setup_constraint(domain, domain, tatum::Time(0.));
tc.set_hold_constraint(domain, domain, tatum::Time(0.));
//Constrain all I/Os with zero input/output delay
constrain_all_ios(netlist, lookup, tc, domain, domain, tatum::Time(0.), tatum::Time(0.));
//Mark constant generator timing nodes
mark_constant_generators(netlist, lookup, tc);
}
//Apply the default timing constraints for circuits with a single netlist clock
void apply_single_clock_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
const AtomPinId clock_driver,
tatum::TimingConstraints& tc) {
AtomNetId clock_net = netlist.pin_net(clock_driver);
std::string clock_name = netlist.net_name(clock_net);
VTR_LOG("Setting default timing constraints:\n");
VTR_LOG(" * constrain all primay inputs and primary outputs on netlist clock '%s'\n", clock_name.c_str());
VTR_LOG(" * optimize netlist clock to run as fast as possible\n");
//Create the netlist clock with period 0
tatum::DomainId domain = tc.create_clock_domain(clock_name);
tc.set_setup_constraint(domain, domain, tatum::Time(0.));
tc.set_hold_constraint(domain, domain, tatum::Time(0.));
//Mark the clock domain source
AtomPinId clock_driver_pin = netlist.net_driver(clock_net);
tatum::NodeId clock_source = lookup.atom_pin_tnode(clock_driver_pin);
VTR_ASSERT(clock_source);
tc.set_clock_domain_source(clock_source, domain);
//Constrain all I/Os with zero input/output delay
constrain_all_ios(netlist, lookup, tc, domain, domain, tatum::Time(0.), tatum::Time(0.));
//Mark constant generator timing nodes
mark_constant_generators(netlist, lookup, tc);
}
//Apply the default timing constraints for circuits with multiple netlist clocks
void apply_multi_clock_default_timing_constraints(const AtomNetlist& netlist,
const AtomLookup& lookup,
const std::set<AtomPinId>& clock_drivers,
tatum::TimingConstraints& tc) {
std::string virtual_clock_name = "virtual_io_clock";
VTR_LOG("Setting default timing constraints:\n");
VTR_LOG(" * constrain all primay inputs and primary outputs on a virtual external clock '%s'\n", virtual_clock_name.c_str());
VTR_LOG(" * optimize all netlist and virtual clocks to run as fast as possible\n");
VTR_LOG(" * ignore cross netlist clock domain timing paths\n");
//Create a virtual clock, with 0 period
tatum::DomainId virtual_clock = tc.create_clock_domain(virtual_clock_name);
tc.set_setup_constraint(virtual_clock, virtual_clock, tatum::Time(0.));
tc.set_hold_constraint(virtual_clock, virtual_clock, tatum::Time(0.));
//Constrain all I/Os with zero input/output delay t the virtual clock
constrain_all_ios(netlist, lookup, tc, virtual_clock, virtual_clock, tatum::Time(0.), tatum::Time(0.));
//Create each of the netlist clocks, and constrain it to period 0. Do not analyze cross-domain paths
for (AtomPinId clock_driver : clock_drivers) {
AtomNetId clock_net = netlist.pin_net(clock_driver);
//Create the clock
std::string clock_name = netlist.net_name(clock_net);
tatum::DomainId clock = tc.create_clock_domain(clock_name);
//Mark the clock domain source
tatum::NodeId clock_source = lookup.atom_pin_tnode(clock_driver);
VTR_ASSERT(clock_source);
tc.set_clock_domain_source(clock_source, clock);
//Do not analyze cross-domain timing paths (except to/from virtual clock)
tc.set_setup_constraint(clock, clock, tatum::Time(0.)); //Intra-domain
tc.set_setup_constraint(clock, virtual_clock, tatum::Time(0.)); //netlist to virtual
tc.set_setup_constraint(virtual_clock, clock, tatum::Time(0.)); //virtual to netlist
tc.set_hold_constraint(clock, clock, tatum::Time(0.)); //Intra-domain
tc.set_hold_constraint(clock, virtual_clock, tatum::Time(0.)); //netlist to virtual
tc.set_hold_constraint(virtual_clock, clock, tatum::Time(0.)); //virtual to netlist
}
//Mark constant generator timing nodes
mark_constant_generators(netlist, lookup, tc);
}
//Look through the netlist to find any constant generators, and mark them as
//constant generators in the timing constraints
void mark_constant_generators(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc) {
for (AtomPinId pin : netlist.pins()) {
if (netlist.pin_is_constant(pin)) {
tatum::NodeId tnode = lookup.atom_pin_tnode(pin);
VTR_ASSERT(tnode);
tc.set_constant_generator(tnode);
}
}
}
//Constrain all primary inputs and primary outputs to the specifed clock domains and delays
void constrain_all_ios(const AtomNetlist& netlist,
const AtomLookup& lookup,
tatum::TimingConstraints& tc,
tatum::DomainId input_domain,
tatum::DomainId output_domain,
tatum::Time input_delay,
tatum::Time output_delay) {
for (AtomBlockId blk : netlist.blocks()) {
AtomBlockType type = netlist.block_type(blk);
if (type == AtomBlockType::INPAD || type == AtomBlockType::OUTPAD) {
//Get the pin
if (netlist.block_pins(blk).size() == 1) {
AtomPinId pin = *netlist.block_pins(blk).begin();
//Find the associated tnode
tatum::NodeId tnode = lookup.atom_pin_tnode(pin);
//Constrain it
if (type == AtomBlockType::INPAD) {
tc.set_input_constraint(tnode, input_domain, tatum::DelayType::MAX, input_delay);
tc.set_input_constraint(tnode, input_domain, tatum::DelayType::MIN, input_delay);
} else {
VTR_ASSERT(type == AtomBlockType::OUTPAD);
tc.set_output_constraint(tnode, output_domain, tatum::DelayType::MAX, output_delay);
tc.set_output_constraint(tnode, output_domain, tatum::DelayType::MIN, output_delay);
}
} else {
VTR_ASSERT_MSG(netlist.block_pins(blk).size() == 0, "Unconnected I/O");
}
}
}
}
std::map<std::string, AtomPinId> find_netlist_primary_ios(const AtomNetlist& netlist) {
std::map<std::string, AtomPinId> primary_inputs;
for (AtomBlockId blk : netlist.blocks()) {
auto type = netlist.block_type(blk);
if (type == AtomBlockType::INPAD || type == AtomBlockType::OUTPAD) {
VTR_ASSERT(netlist.block_pins(blk).size() == 1);
AtomPinId pin = *netlist.block_pins(blk).begin();
std::string orig_name = orig_blif_name(netlist.block_name(blk));
VTR_ASSERT(!primary_inputs.count(orig_name));
primary_inputs[orig_name] = pin;
}
}
return primary_inputs;
}
//Trim off the prefix added to blif names to make outputs unique
std::string orig_blif_name(std::string name) {
constexpr const char* BLIF_UNIQ_PREFIX = "out:";
if (name.find(BLIF_UNIQ_PREFIX) == 0) { //Starts with prefix
name = vtr::replace_first(name, BLIF_UNIQ_PREFIX, ""); //Remove prefix
}
return name;
}
//Converts a glob pattern to a std::regex
std::regex glob_pattern_to_regex(const std::string& glob_pattern) {
//In glob (i.e. unix-shell style):
// '*' is a wildcard match of zero or more instances of any characters
//
//In regex:
// '*' matches zero or more of the preceeding character
// '.' matches any character
//
//To convert a glob to a regex we need to:
// Convert '.' to "\.", so literal '.' in glob is treated as literal in the regex
// Convert '*' to ".*" so literal '*' in glob matches any sequence
std::string regex_str = vtr::replace_all(glob_pattern, ".", "\\.");
regex_str = vtr::replace_all(regex_str, "*", ".*");
return std::regex(regex_str);
}