2018-06-20 17:44:28 +08:00
/*
* nextpnr - - Next Generation Place and Route
*
* Copyright ( C ) 2018 David Shah < david @ symbioticeda . com >
2018-08-07 03:14:00 +08:00
* Copyright ( C ) 2018 Eddie Hung < eddieh @ ece . ubc . ca >
2018-06-20 17:44:28 +08:00
*
* Permission to use , copy , modify , and / or distribute this software for any
* purpose with or without fee is hereby granted , provided that the above
* copyright notice and this permission notice appear in all copies .
*
* THE SOFTWARE IS PROVIDED " AS IS " AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL , DIRECT , INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE , DATA OR PROFITS , WHETHER IN AN
* ACTION OF CONTRACT , NEGLIGENCE OR OTHER TORTIOUS ACTION , ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
*
*/
# include "timing.h"
2018-06-20 17:53:49 +08:00
# include <algorithm>
2018-08-07 08:35:23 +08:00
# include <boost/range/adaptor/reversed.hpp>
2018-08-09 17:00:24 +08:00
# include <deque>
2018-11-03 00:56:53 +08:00
# include <map>
2018-06-20 17:44:28 +08:00
# include <unordered_map>
# include <utility>
2018-06-20 17:53:49 +08:00
# include "log.h"
2018-07-21 16:55:20 +08:00
# include "util.h"
2019-01-27 10:24:30 +08:00
# include <fstream>
2018-06-20 17:44:28 +08:00
2018-06-20 17:53:49 +08:00
NEXTPNR_NAMESPACE_BEGIN
2018-10-30 18:07:37 +08:00
namespace {
2018-11-03 00:56:53 +08:00
struct ClockEvent
{
IdString clock ;
ClockEdge edge ;
2018-11-03 01:26:14 +08:00
bool operator = = ( const ClockEvent & other ) const { return clock = = other . clock & & edge = = other . edge ; }
2018-11-03 00:56:53 +08:00
} ;
2018-10-30 18:07:37 +08:00
2018-11-03 00:56:53 +08:00
struct ClockPair
{
ClockEvent start , end ;
2018-11-03 01:26:14 +08:00
bool operator = = ( const ClockPair & other ) const { return start = = other . start & & end = = other . end ; }
2018-11-03 00:56:53 +08:00
} ;
} // namespace
2018-10-30 18:07:37 +08:00
NEXTPNR_NAMESPACE_END
namespace std {
2018-11-03 00:56:53 +08:00
template < > struct hash < NEXTPNR_NAMESPACE_PREFIX ClockEvent >
{
std : : size_t operator ( ) ( const NEXTPNR_NAMESPACE_PREFIX ClockEvent & obj ) const noexcept
{
std : : size_t seed = 0 ;
boost : : hash_combine ( seed , hash < NEXTPNR_NAMESPACE_PREFIX IdString > ( ) ( obj . clock ) ) ;
boost : : hash_combine ( seed , hash < int > ( ) ( int ( obj . edge ) ) ) ;
return seed ;
}
} ;
2018-10-30 18:07:37 +08:00
2018-11-03 00:56:53 +08:00
template < > struct hash < NEXTPNR_NAMESPACE_PREFIX ClockPair >
{
std : : size_t operator ( ) ( const NEXTPNR_NAMESPACE_PREFIX ClockPair & obj ) const noexcept
{
std : : size_t seed = 0 ;
boost : : hash_combine ( seed , hash < NEXTPNR_NAMESPACE_PREFIX ClockEvent > ( ) ( obj . start ) ) ;
boost : : hash_combine ( seed , hash < NEXTPNR_NAMESPACE_PREFIX ClockEvent > ( ) ( obj . start ) ) ;
return seed ;
}
} ;
2018-10-30 18:07:37 +08:00
2018-11-03 00:56:53 +08:00
} // namespace std
2018-10-30 18:07:37 +08:00
NEXTPNR_NAMESPACE_BEGIN
2018-08-04 14:42:25 +08:00
typedef std : : vector < const PortRef * > PortRefVector ;
2018-08-05 09:55:03 +08:00
typedef std : : map < int , unsigned > DelayFrequency ;
2018-07-25 00:20:07 +08:00
2018-11-03 00:56:53 +08:00
struct CriticalPath
{
PortRefVector ports ;
delay_t path_delay ;
delay_t path_period ;
} ;
typedef std : : unordered_map < ClockPair , CriticalPath > CriticalPathMap ;
2018-12-01 19:54:26 +08:00
typedef std : : unordered_map < IdString , NetCriticalityInfo > NetCriticalityMap ;
2018-11-03 00:56:53 +08:00
2018-08-04 13:39:25 +08:00
struct Timing
2018-06-20 17:44:28 +08:00
{
2018-08-04 13:39:25 +08:00
Context * ctx ;
2018-08-06 22:18:06 +08:00
bool net_delays ;
2018-08-04 13:39:25 +08:00
bool update ;
delay_t min_slack ;
2018-11-03 00:56:53 +08:00
CriticalPathMap * crit_path ;
2018-08-04 14:39:42 +08:00
DelayFrequency * slack_histogram ;
2018-12-01 19:54:26 +08:00
NetCriticalityMap * net_crit ;
2018-12-05 20:31:35 +08:00
IdString async_clock ;
2018-10-30 18:07:37 +08:00
2018-08-07 08:35:23 +08:00
struct TimingData
{
2018-08-06 13:38:54 +08:00
TimingData ( ) : max_arrival ( ) , max_path_length ( ) , min_remaining_budget ( ) { }
2018-11-03 00:56:53 +08:00
TimingData ( delay_t max_arrival ) : max_arrival ( max_arrival ) , max_path_length ( ) , min_remaining_budget ( ) { }
delay_t max_arrival ;
2018-08-06 13:38:54 +08:00
unsigned max_path_length = 0 ;
delay_t min_remaining_budget ;
2018-09-16 06:17:37 +08:00
bool false_startpoint = false ;
2018-12-01 19:54:26 +08:00
std : : vector < delay_t > min_required ;
2018-11-03 00:56:53 +08:00
std : : unordered_map < ClockEvent , delay_t > arrival_time ;
2018-08-06 13:38:54 +08:00
} ;
2018-11-03 00:56:53 +08:00
Timing ( Context * ctx , bool net_delays , bool update , CriticalPathMap * crit_path = nullptr ,
2018-12-01 19:54:26 +08:00
DelayFrequency * slack_histogram = nullptr , NetCriticalityMap * net_crit = nullptr )
2018-08-06 22:19:32 +08:00
: ctx ( ctx ) , net_delays ( net_delays ) , update ( update ) , min_slack ( 1.0e12 / ctx - > target_freq ) ,
2018-12-01 19:54:26 +08:00
crit_path ( crit_path ) , slack_histogram ( slack_histogram ) , net_crit ( net_crit ) ,
async_clock ( ctx - > id ( " $async$ " ) )
2018-08-04 14:42:25 +08:00
{
}
2018-08-04 13:39:25 +08:00
delay_t walk_paths ( )
{
2018-11-04 22:03:33 +08:00
const auto clk_period = ctx - > getDelayFromNS ( 1.0e9 / ctx - > target_freq ) . maxDelay ( ) ;
2018-08-04 14:42:25 +08:00
2018-08-08 23:01:24 +08:00
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
2018-08-07 08:35:23 +08:00
std : : vector < NetInfo * > topographical_order ;
2018-10-30 18:07:37 +08:00
std : : unordered_map < const NetInfo * , std : : unordered_map < ClockEvent , TimingData > > net_data ;
2018-08-08 23:01:24 +08:00
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
2018-08-07 08:35:23 +08:00
std : : unordered_map < const PortInfo * , unsigned > port_fanin ;
2018-08-06 13:38:54 +08:00
std : : vector < IdString > input_ports ;
2018-08-07 08:35:23 +08:00
std : : vector < const PortInfo * > output_ports ;
2018-08-06 13:38:54 +08:00
for ( auto & cell : ctx - > cells ) {
input_ports . clear ( ) ;
output_ports . clear ( ) ;
2018-08-07 08:35:23 +08:00
for ( auto & port : cell . second - > ports ) {
if ( ! port . second . net )
continue ;
2018-08-06 13:38:54 +08:00
if ( port . second . type = = PORT_OUT )
output_ports . push_back ( & port . second ) ;
else
input_ports . push_back ( port . first ) ;
}
for ( auto o : output_ports ) {
2018-11-03 00:56:53 +08:00
int clocks = 0 ;
TimingPortClass portClass = ctx - > getPortTimingClass ( cell . second . get ( ) , o - > name , clocks ) ;
2018-08-08 23:07:20 +08:00
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
// start-point
2018-08-08 20:58:43 +08:00
if ( portClass = = TMG_REGISTER_OUTPUT ) {
2018-08-06 13:38:54 +08:00
topographical_order . emplace_back ( o - > net ) ;
2018-11-03 00:56:53 +08:00
for ( int i = 0 ; i < clocks ; i + + ) {
TimingClockingInfo clkInfo = ctx - > getPortClockingInfo ( cell . second . get ( ) , o - > name , i ) ;
const NetInfo * clknet = get_net_or_empty ( cell . second . get ( ) , clkInfo . clock_port ) ;
IdString clksig = clknet ? clknet - > name : async_clock ;
net_data [ o - > net ] [ ClockEvent { clksig , clknet ? clkInfo . edge : RISING_EDGE } ] =
TimingData { clkInfo . clockToQ . maxDelay ( ) } ;
}
2018-08-07 08:35:23 +08:00
} else {
2018-08-08 22:49:07 +08:00
if ( portClass = = TMG_STARTPOINT | | portClass = = TMG_GEN_CLOCK | | portClass = = TMG_IGNORE ) {
2018-08-06 13:38:54 +08:00
topographical_order . emplace_back ( o - > net ) ;
2018-09-16 06:17:37 +08:00
TimingData td ;
td . false_startpoint = ( portClass = = TMG_GEN_CLOCK | | portClass = = TMG_IGNORE ) ;
2018-11-03 22:09:27 +08:00
td . max_arrival = 0 ;
2018-11-03 00:56:53 +08:00
net_data [ o - > net ] [ ClockEvent { async_clock , RISING_EDGE } ] = td ;
2018-08-06 13:38:54 +08:00
}
2018-11-03 22:09:27 +08:00
// Don't analyse paths from a clock input to other pins - they will be considered by the
// special-case handling register input/output class ports
if ( portClass = = TMG_CLOCK_INPUT )
continue ;
2018-08-08 23:07:20 +08:00
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
// the current output port, increment fanin counter
2018-08-06 13:38:54 +08:00
for ( auto i : input_ports ) {
DelayInfo comb_delay ;
bool is_path = ctx - > getCellDelay ( cell . second . get ( ) , i , o - > name , comb_delay ) ;
if ( is_path )
port_fanin [ o ] + + ;
}
}
}
}
2018-08-07 08:35:23 +08:00
std : : deque < NetInfo * > queue ( topographical_order . begin ( ) , topographical_order . end ( ) ) ;
2018-08-06 13:38:54 +08:00
2018-08-08 23:01:24 +08:00
// Now walk the design, from the start points identified previously, building up a topographical order
2018-08-06 13:38:54 +08:00
while ( ! queue . empty ( ) ) {
const auto net = queue . front ( ) ;
queue . pop_front ( ) ;
for ( auto & usr : net - > users ) {
2018-11-03 00:56:53 +08:00
int user_clocks ;
TimingPortClass usrClass = ctx - > getPortTimingClass ( usr . cell , usr . port , user_clocks ) ;
2018-08-08 21:07:41 +08:00
if ( usrClass = = TMG_IGNORE | | usrClass = = TMG_CLOCK_INPUT )
continue ;
2018-08-07 08:35:23 +08:00
for ( auto & port : usr . cell - > ports ) {
2018-08-07 10:53:42 +08:00
if ( port . second . type ! = PORT_OUT | | ! port . second . net )
continue ;
2018-11-03 00:56:53 +08:00
int port_clocks ;
TimingPortClass portClass = ctx - > getPortTimingClass ( usr . cell , port . first , port_clocks ) ;
2018-08-08 21:07:41 +08:00
2018-08-07 10:53:42 +08:00
// Skip if this is a clocked output (but allow non-clocked ones)
2018-08-08 21:07:41 +08:00
if ( portClass = = TMG_REGISTER_OUTPUT | | portClass = = TMG_STARTPOINT | | portClass = = TMG_IGNORE | |
portClass = = TMG_GEN_CLOCK )
2018-08-07 10:53:42 +08:00
continue ;
DelayInfo comb_delay ;
bool is_path = ctx - > getCellDelay ( usr . cell , usr . port , port . first , comb_delay ) ;
if ( ! is_path )
continue ;
2018-08-08 23:07:20 +08:00
// Decrement the fanin count, and only add to topographical order if all its fanins have already
// been visited
2018-08-07 10:53:42 +08:00
auto it = port_fanin . find ( & port . second ) ;
NPNR_ASSERT ( it ! = port_fanin . end ( ) ) ;
if ( - - it - > second = = 0 ) {
topographical_order . emplace_back ( port . second . net ) ;
queue . emplace_back ( port . second . net ) ;
port_fanin . erase ( it ) ;
2018-08-06 13:38:54 +08:00
}
}
}
}
2018-08-08 23:01:24 +08:00
// Sanity check to ensure that all ports where fanins were recorded were indeed visited
2018-10-02 00:45:35 +08:00
if ( ! port_fanin . empty ( ) ) {
for ( auto fanin : port_fanin ) {
NetInfo * net = fanin . first - > net ;
if ( net ! = nullptr ) {
log_info ( " remaining fanin includes %s (net %s) \n " , fanin . first - > name . c_str ( ctx ) ,
net - > name . c_str ( ctx ) ) ;
if ( net - > driver . cell ! = nullptr )
log_info ( " driver = %s.%s \n " , net - > driver . cell - > name . c_str ( ctx ) ,
net - > driver . port . c_str ( ctx ) ) ;
for ( auto net_user : net - > users )
2018-10-02 01:20:14 +08:00
log_info ( " user: %s.%s \n " , net_user . cell - > name . c_str ( ctx ) , net_user . port . c_str ( ctx ) ) ;
2018-10-02 00:45:35 +08:00
} else {
log_info ( " remaining fanin includes %s (no net) \n " , fanin . first - > name . c_str ( ctx ) ) ;
}
}
2018-11-12 05:19:50 +08:00
if ( ctx - > force )
2018-11-16 21:25:51 +08:00
log_warning ( " timing analysis failed due to presence of combinatorial loops, incomplete specification "
" of timing ports, etc. \n " ) ;
2018-11-12 05:19:50 +08:00
else
2018-11-16 21:25:51 +08:00
log_error ( " timing analysis failed due to presence of combinatorial loops, incomplete specification of "
" timing ports, etc. \n " ) ;
2018-10-02 00:45:35 +08:00
}
2018-08-07 03:03:58 +08:00
2018-08-08 23:01:24 +08:00
// Go forwards topographically to find the maximum arrival time and max path length for each net
2018-08-06 13:38:54 +08:00
for ( auto net : topographical_order ) {
2018-11-03 03:13:50 +08:00
if ( ! net_data . count ( net ) )
continue ;
2018-10-30 18:07:37 +08:00
auto & nd_map = net_data . at ( net ) ;
for ( auto & startdomain : nd_map ) {
ClockEvent start_clk = startdomain . first ;
auto & nd = startdomain . second ;
2018-11-03 03:13:50 +08:00
if ( nd . false_startpoint )
continue ;
2018-10-30 18:07:37 +08:00
const auto net_arrival = nd . max_arrival ;
const auto net_length_plus_one = nd . max_path_length + 1 ;
nd . min_remaining_budget = clk_period ;
for ( auto & usr : net - > users ) {
2018-11-03 00:56:53 +08:00
int port_clocks ;
TimingPortClass portClass = ctx - > getPortTimingClass ( usr . cell , usr . port , port_clocks ) ;
auto net_delay = net_delays ? ctx - > getNetinfoRouteDelay ( net , usr ) : delay_t ( ) ;
auto usr_arrival = net_arrival + net_delay ;
2019-01-21 19:58:49 +08:00
if ( portClass = = TMG_ENDPOINT | | portClass = = TMG_IGNORE | | portClass = = TMG_CLOCK_INPUT ) {
2018-11-03 00:56:53 +08:00
// Skip
2018-10-30 18:07:37 +08:00
} else {
auto budget_override = ctx - > getBudgetOverride ( net , usr , net_delay ) ;
// Iterate over all output ports on the same cell as the sink
for ( auto port : usr . cell - > ports ) {
if ( port . second . type ! = PORT_OUT | | ! port . second . net )
continue ;
DelayInfo comb_delay ;
// Look up delay through this path
bool is_path = ctx - > getCellDelay ( usr . cell , usr . port , port . first , comb_delay ) ;
if ( ! is_path )
continue ;
auto & data = net_data [ port . second . net ] [ start_clk ] ;
auto & arrival = data . max_arrival ;
arrival = std : : max ( arrival , usr_arrival + comb_delay . maxDelay ( ) ) ;
if ( ! budget_override ) { // Do not increment path length if budget overriden since it doesn't
// require a share of the slack
auto & path_length = data . max_path_length ;
path_length = std : : max ( path_length , net_length_plus_one ) ;
}
2018-08-06 13:38:54 +08:00
}
}
}
}
}
2018-11-03 00:56:53 +08:00
std : : unordered_map < ClockPair , std : : pair < delay_t , NetInfo * > > crit_nets ;
2018-08-07 05:14:41 +08:00
2018-08-08 23:07:20 +08:00
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path
2018-08-06 13:38:54 +08:00
for ( auto net : boost : : adaptors : : reverse ( topographical_order ) ) {
2018-11-03 03:13:50 +08:00
if ( ! net_data . count ( net ) )
continue ;
2018-11-03 00:56:53 +08:00
auto & nd_map = net_data . at ( net ) ;
for ( auto & startdomain : nd_map ) {
auto & nd = startdomain . second ;
// Ignore false startpoints
if ( nd . false_startpoint )
continue ;
const delay_t net_length_plus_one = nd . max_path_length + 1 ;
auto & net_min_remaining_budget = nd . min_remaining_budget ;
for ( auto & usr : net - > users ) {
auto net_delay = net_delays ? ctx - > getNetinfoRouteDelay ( net , usr ) : delay_t ( ) ;
auto budget_override = ctx - > getBudgetOverride ( net , usr , net_delay ) ;
int port_clocks ;
TimingPortClass portClass = ctx - > getPortTimingClass ( usr . cell , usr . port , port_clocks ) ;
if ( portClass = = TMG_REGISTER_INPUT | | portClass = = TMG_ENDPOINT ) {
auto process_endpoint = [ & ] ( IdString clksig , ClockEdge edge , delay_t setup ) {
const auto net_arrival = nd . max_arrival ;
const auto endpoint_arrival = net_arrival + net_delay + setup ;
delay_t period ;
2018-11-12 21:42:25 +08:00
// Set default period
2018-11-03 00:56:53 +08:00
if ( edge = = startdomain . first . edge ) {
period = clk_period ;
} else {
period = clk_period / 2 ;
}
2018-11-12 21:42:25 +08:00
if ( clksig ! = async_clock ) {
if ( ctx - > nets . at ( clksig ) - > clkconstr ) {
if ( edge = = startdomain . first . edge ) {
// same edge
period = ctx - > nets . at ( clksig ) - > clkconstr - > period . minDelay ( ) ;
} else if ( edge = = RISING_EDGE ) {
// falling -> rising
period = ctx - > nets . at ( clksig ) - > clkconstr - > low . minDelay ( ) ;
} else if ( edge = = FALLING_EDGE ) {
// rising -> falling
period = ctx - > nets . at ( clksig ) - > clkconstr - > high . minDelay ( ) ;
}
}
}
2018-11-14 16:46:10 +08:00
auto path_budget = period - endpoint_arrival ;
2018-11-03 00:56:53 +08:00
if ( update ) {
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one ;
usr . budget = std : : min ( usr . budget , net_delay + budget_share ) ;
net_min_remaining_budget =
std : : min ( net_min_remaining_budget , path_budget - budget_share ) ;
}
if ( path_budget < min_slack )
min_slack = path_budget ;
if ( slack_histogram ) {
int slack_ps = ctx - > getDelayNS ( path_budget ) * 1000 ;
( * slack_histogram ) [ slack_ps ] + + ;
}
ClockEvent dest_ev { clksig , edge } ;
ClockPair clockPair { startdomain . first , dest_ev } ;
nd . arrival_time [ dest_ev ] = std : : max ( nd . arrival_time [ dest_ev ] , endpoint_arrival ) ;
if ( crit_path ) {
if ( ! crit_nets . count ( clockPair ) | | crit_nets . at ( clockPair ) . first < endpoint_arrival ) {
crit_nets [ clockPair ] = std : : make_pair ( endpoint_arrival , net ) ;
( * crit_path ) [ clockPair ] . path_delay = endpoint_arrival ;
2018-11-04 22:03:33 +08:00
( * crit_path ) [ clockPair ] . path_period = period ;
2018-11-03 00:56:53 +08:00
( * crit_path ) [ clockPair ] . ports . clear ( ) ;
( * crit_path ) [ clockPair ] . ports . push_back ( & usr ) ;
}
}
} ;
if ( portClass = = TMG_REGISTER_INPUT ) {
for ( int i = 0 ; i < port_clocks ; i + + ) {
TimingClockingInfo clkInfo = ctx - > getPortClockingInfo ( usr . cell , usr . port , i ) ;
const NetInfo * clknet = get_net_or_empty ( usr . cell , clkInfo . clock_port ) ;
IdString clksig = clknet ? clknet - > name : async_clock ;
2018-11-03 22:09:27 +08:00
process_endpoint ( clksig , clknet ? clkInfo . edge : RISING_EDGE , clkInfo . setup . maxDelay ( ) ) ;
2018-11-03 00:56:53 +08:00
}
} else {
process_endpoint ( async_clock , RISING_EDGE , 0 ) ;
}
} else if ( update ) {
2018-08-06 13:38:54 +08:00
2018-11-03 00:56:53 +08:00
// Iterate over all output ports on the same cell as the sink
for ( const auto & port : usr . cell - > ports ) {
if ( port . second . type ! = PORT_OUT | | ! port . second . net )
continue ;
DelayInfo comb_delay ;
bool is_path = ctx - > getCellDelay ( usr . cell , usr . port , port . first , comb_delay ) ;
if ( ! is_path )
continue ;
2018-11-03 01:26:14 +08:00
if ( net_data . count ( port . second . net ) & &
net_data . at ( port . second . net ) . count ( startdomain . first ) ) {
auto path_budget =
net_data . at ( port . second . net ) . at ( startdomain . first ) . min_remaining_budget ;
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one ;
usr . budget = std : : min ( usr . budget , net_delay + budget_share ) ;
net_min_remaining_budget =
std : : min ( net_min_remaining_budget , path_budget - budget_share ) ;
}
2018-08-07 05:14:41 +08:00
}
}
2018-08-06 13:38:54 +08:00
}
}
}
2018-08-07 05:14:41 +08:00
if ( crit_path ) {
// Walk backwards from the most critical net
2018-11-03 00:56:53 +08:00
for ( auto crit_pair : crit_nets ) {
NetInfo * crit_net = crit_pair . second . second ;
auto & cp_ports = ( * crit_path ) [ crit_pair . first ] . ports ;
while ( crit_net ) {
const PortInfo * crit_ipin = nullptr ;
delay_t max_arrival = std : : numeric_limits < delay_t > : : min ( ) ;
// Look at all input ports on its driving cell
for ( const auto & port : crit_net - > driver . cell - > ports ) {
if ( port . second . type ! = PORT_IN | | ! port . second . net )
continue ;
DelayInfo comb_delay ;
bool is_path =
ctx - > getCellDelay ( crit_net - > driver . cell , port . first , crit_net - > driver . port , comb_delay ) ;
if ( ! is_path )
continue ;
// If input port is influenced by a clock, skip
int port_clocks ;
TimingPortClass portClass =
ctx - > getPortTimingClass ( crit_net - > driver . cell , port . first , port_clocks ) ;
2018-12-13 20:10:18 +08:00
if ( portClass = = TMG_CLOCK_INPUT | | portClass = = TMG_ENDPOINT | | portClass = = TMG_IGNORE | |
portClass = = TMG_REGISTER_INPUT )
2018-11-03 00:56:53 +08:00
continue ;
// And find the fanin net with the latest arrival time
2018-11-03 22:09:27 +08:00
if ( net_data . count ( port . second . net ) & &
net_data . at ( port . second . net ) . count ( crit_pair . first . start ) ) {
2018-12-13 20:10:18 +08:00
auto net_arrival = net_data . at ( port . second . net ) . at ( crit_pair . first . start ) . max_arrival ;
if ( net_delays ) {
for ( auto & user : port . second . net - > users )
if ( user . port = = port . first & & user . cell = = crit_net - > driver . cell ) {
net_arrival + = ctx - > getNetinfoRouteDelay ( port . second . net , user ) ;
break ;
}
}
net_arrival + = comb_delay . maxDelay ( ) ;
2018-11-03 01:26:14 +08:00
if ( net_arrival > max_arrival ) {
max_arrival = net_arrival ;
crit_ipin = & port . second ;
}
2018-11-03 00:56:53 +08:00
}
2018-08-07 05:14:41 +08:00
}
2018-11-03 00:56:53 +08:00
if ( ! crit_ipin )
2018-08-07 05:14:41 +08:00
break ;
2018-11-03 00:56:53 +08:00
// Now convert PortInfo* into a PortRef*
for ( auto & usr : crit_ipin - > net - > users ) {
if ( usr . cell - > name = = crit_net - > driver . cell - > name & & usr . port = = crit_ipin - > name ) {
cp_ports . push_back ( & usr ) ;
break ;
}
2018-08-07 05:14:41 +08:00
}
2018-11-03 00:56:53 +08:00
crit_net = crit_ipin - > net ;
2018-08-07 05:14:41 +08:00
}
2018-11-03 00:56:53 +08:00
std : : reverse ( cp_ports . begin ( ) , cp_ports . end ( ) ) ;
2018-08-07 05:14:41 +08:00
}
}
2018-12-01 19:54:26 +08:00
if ( net_crit ) {
NPNR_ASSERT ( crit_path ) ;
// Go through in reverse topographical order to set required times
for ( auto net : boost : : adaptors : : reverse ( topographical_order ) ) {
if ( ! net_data . count ( net ) )
continue ;
auto & nd_map = net_data . at ( net ) ;
for ( auto & startdomain : nd_map ) {
auto & nd = startdomain . second ;
if ( nd . false_startpoint )
continue ;
2018-12-02 20:23:18 +08:00
if ( startdomain . first . clock = = async_clock )
continue ;
2018-12-01 19:54:26 +08:00
if ( nd . min_required . empty ( ) )
nd . min_required . resize ( net - > users . size ( ) , std : : numeric_limits < delay_t > : : max ( ) ) ;
delay_t net_min_required = std : : numeric_limits < delay_t > : : max ( ) ;
for ( size_t i = 0 ; i < net - > users . size ( ) ; i + + ) {
auto & usr = net - > users . at ( i ) ;
auto net_delay = ctx - > getNetinfoRouteDelay ( net , usr ) ;
int port_clocks ;
TimingPortClass portClass = ctx - > getPortTimingClass ( usr . cell , usr . port , port_clocks ) ;
if ( portClass = = TMG_REGISTER_INPUT | | portClass = = TMG_ENDPOINT ) {
auto process_endpoint = [ & ] ( IdString clksig , ClockEdge edge , delay_t setup ) {
delay_t period ;
// Set default period
if ( edge = = startdomain . first . edge ) {
period = clk_period ;
} else {
period = clk_period / 2 ;
}
if ( clksig ! = async_clock ) {
if ( ctx - > nets . at ( clksig ) - > clkconstr ) {
if ( edge = = startdomain . first . edge ) {
// same edge
period = ctx - > nets . at ( clksig ) - > clkconstr - > period . minDelay ( ) ;
} else if ( edge = = RISING_EDGE ) {
// falling -> rising
period = ctx - > nets . at ( clksig ) - > clkconstr - > low . minDelay ( ) ;
} else if ( edge = = FALLING_EDGE ) {
// rising -> falling
period = ctx - > nets . at ( clksig ) - > clkconstr - > high . minDelay ( ) ;
}
}
}
nd . min_required . at ( i ) = std : : min ( period - setup , nd . min_required . at ( i ) ) ;
} ;
if ( portClass = = TMG_REGISTER_INPUT ) {
for ( int j = 0 ; j < port_clocks ; j + + ) {
TimingClockingInfo clkInfo = ctx - > getPortClockingInfo ( usr . cell , usr . port , j ) ;
const NetInfo * clknet = get_net_or_empty ( usr . cell , clkInfo . clock_port ) ;
IdString clksig = clknet ? clknet - > name : async_clock ;
process_endpoint ( clksig , clknet ? clkInfo . edge : RISING_EDGE ,
clkInfo . setup . maxDelay ( ) ) ;
}
} else {
process_endpoint ( async_clock , RISING_EDGE , 0 ) ;
}
}
net_min_required = std : : min ( net_min_required , nd . min_required . at ( i ) - net_delay ) ;
}
PortRef & drv = net - > driver ;
if ( drv . cell = = nullptr )
continue ;
for ( const auto & port : drv . cell - > ports ) {
if ( port . second . type ! = PORT_IN | | ! port . second . net )
continue ;
DelayInfo comb_delay ;
bool is_path = ctx - > getCellDelay ( drv . cell , port . first , drv . port , comb_delay ) ;
if ( ! is_path )
continue ;
2018-12-02 22:14:44 +08:00
int cc ;
auto pclass = ctx - > getPortTimingClass ( drv . cell , port . first , cc ) ;
if ( pclass ! = TMG_COMB_INPUT )
continue ;
2018-12-01 19:54:26 +08:00
NetInfo * sink_net = port . second . net ;
if ( net_data . count ( sink_net ) & & net_data . at ( sink_net ) . count ( startdomain . first ) ) {
auto & sink_nd = net_data . at ( sink_net ) . at ( startdomain . first ) ;
if ( sink_nd . min_required . empty ( ) )
sink_nd . min_required . resize ( sink_net - > users . size ( ) ,
std : : numeric_limits < delay_t > : : max ( ) ) ;
for ( size_t i = 0 ; i < sink_net - > users . size ( ) ; i + + ) {
auto & user = sink_net - > users . at ( i ) ;
if ( user . cell = = drv . cell & & user . port = = port . first ) {
sink_nd . min_required . at ( i ) = net_min_required - comb_delay . maxDelay ( ) ;
break ;
}
}
}
}
}
}
std : : unordered_map < ClockEvent , delay_t > worst_slack ;
// Assign slack values
for ( auto & net_entry : net_data ) {
const NetInfo * net = net_entry . first ;
for ( auto & startdomain : net_entry . second ) {
auto & nd = startdomain . second ;
2018-12-02 20:23:18 +08:00
if ( startdomain . first . clock = = async_clock )
continue ;
2018-12-01 19:54:26 +08:00
if ( nd . min_required . empty ( ) )
continue ;
auto & nc = ( * net_crit ) [ net - > name ] ;
if ( nc . slack . empty ( ) )
nc . slack . resize ( net - > users . size ( ) , std : : numeric_limits < delay_t > : : max ( ) ) ;
2018-12-02 23:49:24 +08:00
#if 0
2018-12-02 22:14:44 +08:00
if ( ctx - > debug )
log_info ( " Net %s cd %s \n " , net - > name . c_str ( ctx ) , startdomain . first . clock . c_str ( ctx ) ) ;
2018-12-02 23:49:24 +08:00
# endif
2018-12-01 19:54:26 +08:00
for ( size_t i = 0 ; i < net - > users . size ( ) ; i + + ) {
delay_t slack = nd . min_required . at ( i ) -
( nd . max_arrival + ctx - > getNetinfoRouteDelay ( net , net - > users . at ( i ) ) ) ;
2018-12-02 23:49:24 +08:00
#if 0
2018-12-02 22:14:44 +08:00
if ( ctx - > debug )
log_info ( " user %s.%s required %.02fns arrival %.02f route %.02f slack %.02f \n " ,
net - > users . at ( i ) . cell - > name . c_str ( ctx ) , net - > users . at ( i ) . port . c_str ( ctx ) ,
ctx - > getDelayNS ( nd . min_required . at ( i ) ) , ctx - > getDelayNS ( nd . max_arrival ) ,
ctx - > getDelayNS ( ctx - > getNetinfoRouteDelay ( net , net - > users . at ( i ) ) ) , ctx - > getDelayNS ( slack ) ) ;
2018-12-02 23:49:24 +08:00
# endif
2018-12-01 19:54:26 +08:00
if ( worst_slack . count ( startdomain . first ) )
worst_slack . at ( startdomain . first ) = std : : min ( worst_slack . at ( startdomain . first ) , slack ) ;
else
worst_slack [ startdomain . first ] = slack ;
2018-12-05 20:31:35 +08:00
nc . slack . at ( i ) = slack ;
2018-12-01 19:54:26 +08:00
}
2018-12-02 22:14:44 +08:00
if ( ctx - > debug )
log_break ( ) ;
2018-12-01 19:54:26 +08:00
}
}
// Assign criticality values
for ( auto & net_entry : net_data ) {
const NetInfo * net = net_entry . first ;
for ( auto & startdomain : net_entry . second ) {
2018-12-02 20:23:18 +08:00
if ( startdomain . first . clock = = async_clock )
continue ;
2018-12-01 19:54:26 +08:00
auto & nd = startdomain . second ;
if ( nd . min_required . empty ( ) )
continue ;
auto & nc = ( * net_crit ) [ net - > name ] ;
if ( nc . slack . empty ( ) )
continue ;
if ( nc . criticality . empty ( ) )
nc . criticality . resize ( net - > users . size ( ) , 0 ) ;
// Only consider intra-clock paths for criticality
if ( ! crit_path - > count ( ClockPair { startdomain . first , startdomain . first } ) )
continue ;
delay_t dmax = crit_path - > at ( ClockPair { startdomain . first , startdomain . first } ) . path_delay ;
for ( size_t i = 0 ; i < net - > users . size ( ) ; i + + ) {
2018-12-02 20:23:18 +08:00
float criticality = 1.0f - ( float ( nc . slack . at ( i ) - worst_slack . at ( startdomain . first ) ) / dmax ) ;
2018-12-05 20:31:35 +08:00
nc . criticality . at ( i ) = criticality ;
2018-12-01 19:54:26 +08:00
}
2018-12-05 20:31:35 +08:00
nc . max_path_length = nd . max_path_length ;
nc . cd_worst_slack = worst_slack . at ( startdomain . first ) ;
2018-12-01 19:54:26 +08:00
}
}
2018-12-02 23:49:24 +08:00
#if 0
2018-12-02 21:47:56 +08:00
if ( ctx - > debug ) {
for ( auto & nc : * net_crit ) {
NetInfo * net = ctx - > nets . at ( nc . first ) . get ( ) ;
log_info ( " Net %s maxlen %d worst_slack %.02fns: \n " , nc . first . c_str ( ctx ) , nc . second . max_path_length ,
ctx - > getDelayNS ( nc . second . cd_worst_slack ) ) ;
if ( ! nc . second . criticality . empty ( ) & & ! nc . second . slack . empty ( ) ) {
for ( size_t i = 0 ; i < net - > users . size ( ) ; i + + ) {
log_info ( " user %s.%s slack %.02fns crit %.03f \n " , net - > users . at ( i ) . cell - > name . c_str ( ctx ) ,
net - > users . at ( i ) . port . c_str ( ctx ) , ctx - > getDelayNS ( nc . second . slack . at ( i ) ) ,
nc . second . criticality . at ( i ) ) ;
}
}
log_break ( ) ;
}
}
2018-12-02 23:49:24 +08:00
# endif
2018-12-01 19:54:26 +08:00
}
2019-01-27 10:24:30 +08:00
if ( ctx - > debug ) {
2019-01-28 03:08:57 +08:00
log_info ( " Writing timing.dot \n " ) ;
2019-01-27 10:24:30 +08:00
std : : ofstream f ( " timing.dot " ) ;
f < < " digraph T { " < < std : : endl ;
2019-01-28 03:43:55 +08:00
f < < " \t label=<<font point-size= \" 32 \" >clk_period= " < < clk_period < < " </font> "
< < " <font point-size= \" 24 \" ><i> "
< < " <br/><b>Nodes</b> represent ports, clustered by their cells, and annotated with max arrival times. "
< < " <br/><b>Filled nodes</b> represent timing start points (i.e. flip-flops). "
< < " <br/><b>Solid edges</b> represent inter-cell delays (i.e. nets) and are annotated with net name at its center, as well as net delay/budget at its head. "
< < " <br/><b>Dotted edges</b> represent intra-cell delays and are annotated with this value. "
< < " </i></font>>; "
< < std : : endl ;
2019-01-27 10:24:30 +08:00
f < < " \t labelloc=t; " < < std : : endl ;
2019-01-28 03:20:47 +08:00
// Use the new ranking algorithm in dot to allow ranking of nodes across clusters
2019-01-28 03:08:57 +08:00
f < < " \t newrank=true; " < < std : : endl ;
2019-01-28 03:20:47 +08:00
// For each net, draw an edge from driver -> user and label middle of edge with net name, and head of edge with delay/budget
2019-01-27 10:24:30 +08:00
for ( auto & net : ctx - > nets ) {
2019-01-28 03:20:47 +08:00
// For rendering speed, ignore global nets (e.g. clocks)
2019-01-28 03:08:57 +08:00
if ( ctx - > isGlobalNet ( net . second . get ( ) ) )
2019-01-27 10:24:30 +08:00
continue ;
for ( auto & usr : net . second - > users ) {
2019-01-28 03:08:57 +08:00
f < < " \t \" " < < net . second - > driver . cell - > name . str ( ctx ) < < " . " < < net . second - > driver . port . str ( ctx ) < < " \" -> \" " < < usr . cell - > name . str ( ctx ) < < " . " < < usr . port . str ( ctx ) < < " \" [label= \" " < < net . second - > name . c_str ( ctx ) < < " \" ; headlabel= \" " < < ctx - > getNetinfoRouteDelay ( net . second . get ( ) , usr ) < < " / " < < usr . budget < < " \" ]; " < < std : : endl ;
2019-01-27 10:24:30 +08:00
}
}
2019-01-28 03:20:47 +08:00
// Keep track of IOBs, so they can be placed at top/bottom of graph
2019-01-27 10:24:30 +08:00
std : : vector < std : : string > startpoints , endpoints ;
2019-01-28 03:20:47 +08:00
// Temporary vector for each cell
2019-01-27 10:24:30 +08:00
std : : vector < IdString > input_ports , output_ports ;
2019-01-28 03:20:47 +08:00
// Place all ports of cell in a subgraph/cluster with label
2019-01-27 10:24:30 +08:00
for ( auto & cell : ctx - > cells ) {
f < < " \t subgraph \" cluster_ " < < cell . second - > name . str ( ctx ) < < " \" { " < < std : : endl ;
f < < " \t \t label = \" " < < cell . second - > name . str ( ctx ) < < " \" ; " < < std : : endl ;
2019-01-28 03:20:47 +08:00
// Collect all input/output ports
2019-01-27 10:24:30 +08:00
input_ports . clear ( ) ;
output_ports . clear ( ) ;
for ( auto & port : cell . second - > ports ) {
if ( ! port . second . net ) continue ;
int port_clocks ;
auto portClass = ctx - > getPortTimingClass ( cell . second . get ( ) , port . first , port_clocks ) ;
2019-01-28 03:20:47 +08:00
// For rendering speed, ignore clock inputs (since global nets are not drawn)
2019-01-27 10:24:30 +08:00
if ( portClass = = TMG_CLOCK_INPUT ) continue ;
if ( port . second . type = = PORT_IN ) {
input_ports . push_back ( port . first ) ;
2019-01-28 03:20:47 +08:00
// IOB inputs are endpoints
2019-01-28 03:08:57 +08:00
if ( ctx - > getBelIOB ( cell . second - > bel ) )
2019-01-27 10:24:30 +08:00
endpoints . emplace_back ( cell . second - > name . str ( ctx ) + " . " + port . first . str ( ctx ) ) ;
2019-01-28 03:20:47 +08:00
// Label port
2019-01-28 03:08:57 +08:00
f < < " \t \t " < < " \" " < < cell . second - > name . str ( ctx ) < < " . " < < port . first . str ( ctx ) < < " \" [label = \" " < < port . first . str ( ctx ) ;
2019-01-28 03:20:47 +08:00
// Recover the PortRef from its net
2019-01-28 03:08:57 +08:00
for ( const auto & usr : port . second . net - > users ) {
if ( usr . cell ! = cell . second . get ( ) | | usr . port ! = port . first )
continue ;
2019-01-28 03:20:47 +08:00
// And for each clock event, label node with event as well as max arrival time
2019-01-28 03:08:57 +08:00
for ( const auto & i : net_data . at ( port . second . net ) )
f < < " \\ n " < < ( i . first . edge = = RISING_EDGE ? " posedge " : " negedge " ) < < " " < < i . first . clock . str ( ctx ) < < " @ " < < i . second . max_arrival + ctx - > getNetinfoRouteDelay ( port . second . net , usr ) ;
break ;
}
f < < " \" ] " < < std : : endl ;
2019-01-27 10:24:30 +08:00
}
else {
output_ports . push_back ( port . first ) ;
2019-01-28 03:20:47 +08:00
// Label port
2019-01-28 03:08:57 +08:00
f < < " \t \t " < < " \" " < < cell . second - > name . str ( ctx ) < < " . " < < port . first . str ( ctx ) < < " \" [label = \" " < < port . first . str ( ctx ) ;
2019-01-28 03:43:55 +08:00
// A:d for each clock event, label node with event as well as max arrival time
for ( const auto & i : net_data . at ( port . second . net ) )
2019-01-28 03:08:57 +08:00
f < < " \\ n " < < ( i . first . edge = = RISING_EDGE ? " posedge " : " negedge " ) < < " " < < i . first . clock . str ( ctx ) < < " @ " < < i . second . max_arrival ;
f < < " \" " ;
2019-01-28 03:20:47 +08:00
// IOB outputs are startpoints
2019-01-28 03:08:57 +08:00
if ( ctx - > getBelIOB ( cell . second - > bel ) )
2019-01-27 10:24:30 +08:00
startpoints . emplace_back ( cell . second - > name . str ( ctx ) + " . " + port . first . str ( ctx ) ) ;
2019-01-28 03:08:57 +08:00
else if ( portClass = = TMG_REGISTER_OUTPUT | | portClass = = TMG_STARTPOINT ) {
2019-01-28 03:20:47 +08:00
// Otherwise, draw other timing points differently
2019-01-27 10:24:30 +08:00
f < < " ; shape=parallelogram; style=filled " ;
}
f < < " ]; " < < std : : endl ;
}
2019-01-28 03:20:47 +08:00
// Place port inside subgraph
2019-01-27 10:24:30 +08:00
f < < " \t \t " < < " \" " < < cell . second - > name . str ( ctx ) < < " . " < < port . first . str ( ctx ) < < " \" ; " < < std : : endl ;
}
2019-01-28 03:20:47 +08:00
// Mark all input ports of cells with minimum rank, to appear above all output ports
2019-01-27 10:24:30 +08:00
f < < " \t \t {rank=min " ;
for ( auto i : input_ports )
f < < " ; \" " < < cell . second - > name . str ( ctx ) < < " . " < < i . str ( ctx ) < < " \" " ;
f < < " ;} " < < std : : endl ;
f < < " \t \t {rank=max " ;
for ( auto o : output_ports )
f < < " ; \" " < < cell . second - > name . str ( ctx ) < < " . " < < o . str ( ctx ) < < " \" " ;
f < < " ;} " < < std : : endl ;
2019-01-28 03:20:47 +08:00
// Now enumerate all input -> output ports
2019-01-27 10:24:30 +08:00
for ( auto i : input_ports ) {
for ( auto o : output_ports ) {
2019-01-28 03:20:47 +08:00
// Look for combinatorial delay paths
2019-01-27 10:24:30 +08:00
DelayInfo comb_delay ;
bool is_path = ctx - > getCellDelay ( cell . second . get ( ) , i , o , comb_delay ) ;
2019-01-28 03:20:47 +08:00
if ( is_path ) {
// And add an edge to reflect that, annotated with its delay
f < < " \t \t " < < " \" " < < cell . second - > name . str ( ctx ) < < " . " < < i . str ( ctx ) < < " \" -> \" " < < cell . second - > name . str ( ctx ) < < " . " < < o . str ( ctx ) < < " \" [style=dotted; label= " < < comb_delay . maxDelay ( ) < < " ]; " < < std : : endl ;
}
else {
// Otherwise, look for setup time
2019-01-28 03:08:57 +08:00
int port_clocks ;
auto portClass = ctx - > getPortTimingClass ( cell . second . get ( ) , i , port_clocks ) ;
if ( portClass = = TMG_REGISTER_INPUT ) {
for ( int j = 0 ; j < port_clocks ; j + + ) {
TimingClockingInfo clkInfo = ctx - > getPortClockingInfo ( cell . second . get ( ) , i , j ) ;
f < < " \t \t " < < " \" " < < cell . second - > name . str ( ctx ) < < " . " < < i . str ( ctx ) < < " \" -> \" " < < cell . second - > name . str ( ctx ) < < " . " < < o . str ( ctx ) < < " \" [style=dotted; label= " < < clkInfo . setup . maxDelay ( ) < < " ]; " < < std : : endl ;
}
}
}
2019-01-27 10:24:30 +08:00
}
}
f < < " \t } " < < std : : endl ;
}
2019-01-28 03:20:47 +08:00
// Now force all start points to have minimum rank (i.e. appear at top of graph), and end points to have maximum rank
2019-01-28 03:08:57 +08:00
f < < " \t {rank=min " ;
for ( auto i : startpoints )
f < < " ; \" " < < i < < " \" " ;
f < < " ;} " < < std : : endl ;
f < < " \t {rank=max " ;
for ( auto o : endpoints )
f < < " ; \" " < < o < < " \" " ;
f < < " ;} " < < std : : endl ;
2019-01-27 10:24:30 +08:00
f < < " } " < < std : : endl ;
}
2018-08-04 13:39:25 +08:00
return min_slack ;
2018-06-20 17:53:49 +08:00
}
2018-06-20 17:44:28 +08:00
2018-08-04 13:39:25 +08:00
void assign_budget ( )
{
// Clear delays to a very high value first
for ( auto & net : ctx - > nets ) {
for ( auto & usr : net . second - > users ) {
2018-08-06 13:38:54 +08:00
usr . budget = std : : numeric_limits < delay_t > : : max ( ) ;
2018-06-20 18:21:56 +08:00
}
}
2018-06-20 23:08:57 +08:00
2018-08-04 13:39:25 +08:00
walk_paths ( ) ;
}
} ;
2018-07-26 09:21:39 +08:00
2018-07-29 05:10:48 +08:00
void assign_budget ( Context * ctx , bool quiet )
2018-07-26 09:21:39 +08:00
{
2018-07-29 05:10:48 +08:00
if ( ! quiet ) {
log_break ( ) ;
2018-08-06 22:19:32 +08:00
log_info ( " Annotating ports with timing budgets for target frequency %.2f MHz \n " , ctx - > target_freq / 1e6 ) ;
2018-07-29 05:10:48 +08:00
}
2018-08-06 22:18:06 +08:00
Timing timing ( ctx , ctx - > slack_redist_iter > 0 /* net_delays */ , true /* update */ ) ;
2018-08-04 13:39:25 +08:00
timing . assign_budget ( ) ;
2018-07-26 09:21:39 +08:00
2018-07-29 05:10:48 +08:00
if ( ! quiet | | ctx - > verbose ) {
2018-07-29 03:50:21 +08:00
for ( auto & net : ctx - > nets ) {
for ( auto & user : net . second - > users ) {
// Post-update check
2018-08-04 10:53:32 +08:00
if ( ! ctx - > auto_freq & & user . budget < 0 )
2018-11-22 01:13:53 +08:00
log_info ( " port %s.%s, connected to net '%s', has negative "
" timing budget of %fns \n " ,
user . cell - > name . c_str ( ctx ) , user . port . c_str ( ctx ) , net . first . c_str ( ctx ) ,
ctx - > getDelayNS ( user . budget ) ) ;
else if ( ctx - > debug )
2018-07-22 03:47:09 +08:00
log_info ( " port %s.%s, connected to net '%s', has "
" timing budget of %fns \n " ,
user . cell - > name . c_str ( ctx ) , user . port . c_str ( ctx ) , net . first . c_str ( ctx ) ,
ctx - > getDelayNS ( user . budget ) ) ;
}
2018-07-21 16:55:20 +08:00
}
}
2018-07-27 12:35:37 +08:00
2018-08-08 23:07:20 +08:00
// For slack redistribution, if user has not specified a frequency dynamically adjust the target frequency to be the
// currently achieved maximum
2018-08-04 10:53:32 +08:00
if ( ctx - > auto_freq & & ctx - > slack_redist_iter > 0 ) {
2018-08-08 23:54:25 +08:00
delay_t default_slack = delay_t ( ( 1.0e9 / ctx - > getDelayNS ( 1 ) ) / ctx - > target_freq ) ;
ctx - > target_freq = 1.0e9 / ctx - > getDelayNS ( default_slack - timing . min_slack ) ;
2018-08-04 13:39:25 +08:00
if ( ctx - > verbose )
2018-08-08 23:54:25 +08:00
log_info ( " minimum slack for this assign = %.2f ns, target Fmax for next "
2018-08-04 14:42:25 +08:00
" update = %.2f MHz \n " ,
2018-08-08 23:54:25 +08:00
ctx - > getDelayNS ( timing . min_slack ) , ctx - > target_freq / 1e6 ) ;
2018-07-27 12:35:37 +08:00
}
2018-07-29 05:10:48 +08:00
if ( ! quiet )
log_info ( " Checksum: 0x%08x \n " , ctx - > checksum ( ) ) ;
2018-07-21 16:55:20 +08:00
}
2018-11-22 01:13:53 +08:00
void timing_analysis ( Context * ctx , bool print_histogram , bool print_fmax , bool print_path , bool warn_on_failure )
2018-07-26 09:21:39 +08:00
{
2018-11-03 02:59:04 +08:00
auto format_event = [ ctx ] ( const ClockEvent & e , int field_width = 0 ) {
std : : string value ;
if ( e . clock = = ctx - > id ( " $async$ " ) )
value = std : : string ( " <async> " ) ;
else
value = ( e . edge = = FALLING_EDGE ? std : : string ( " negedge " ) : std : : string ( " posedge " ) ) + e . clock . str ( ctx ) ;
if ( int ( value . length ( ) ) < field_width )
value . insert ( value . length ( ) , field_width - int ( value . length ( ) ) , ' ' ) ;
return value ;
} ;
2018-11-03 00:56:53 +08:00
CriticalPathMap crit_paths ;
2018-08-04 14:39:42 +08:00
DelayFrequency slack_histogram ;
2018-08-04 13:39:25 +08:00
2018-11-03 02:59:04 +08:00
Timing timing ( ctx , true /* net_delays */ , false /* update */ , ( print_path | | print_fmax ) ? & crit_paths : nullptr ,
2018-08-04 14:42:25 +08:00
print_histogram ? & slack_histogram : nullptr ) ;
2018-11-12 22:00:08 +08:00
timing . walk_paths ( ) ;
2018-11-03 02:59:04 +08:00
std : : map < IdString , std : : pair < ClockPair , CriticalPath > > clock_reports ;
2018-11-04 22:26:16 +08:00
std : : map < IdString , double > clock_fmax ;
2018-11-03 02:59:04 +08:00
std : : vector < ClockPair > xclock_paths ;
2018-11-14 16:46:10 +08:00
std : : set < IdString > empty_clocks ; // set of clocks with no interior paths
2018-11-03 02:59:04 +08:00
if ( print_path | | print_fmax ) {
2018-11-14 16:46:10 +08:00
for ( auto path : crit_paths ) {
const ClockEvent & a = path . first . start ;
const ClockEvent & b = path . first . end ;
empty_clocks . insert ( a . clock ) ;
empty_clocks . insert ( b . clock ) ;
}
2018-11-03 00:56:53 +08:00
for ( auto path : crit_paths ) {
const ClockEvent & a = path . first . start ;
const ClockEvent & b = path . first . end ;
if ( a . clock ! = b . clock | | a . clock = = ctx - > id ( " $async$ " ) )
continue ;
2018-11-04 22:26:16 +08:00
double Fmax ;
2018-11-14 16:46:10 +08:00
empty_clocks . erase ( a . clock ) ;
2018-11-04 22:26:16 +08:00
if ( a . edge = = b . edge )
Fmax = 1000 / ctx - > getDelayNS ( path . second . path_delay ) ;
else
Fmax = 500 / ctx - > getDelayNS ( path . second . path_delay ) ;
2018-11-04 22:51:48 +08:00
if ( ! clock_fmax . count ( a . clock ) | | Fmax < clock_fmax . at ( a . clock ) ) {
2018-11-03 00:56:53 +08:00
clock_reports [ a . clock ] = path ;
2018-11-04 22:26:16 +08:00
clock_fmax [ a . clock ] = Fmax ;
2018-11-03 00:56:53 +08:00
}
}
2018-11-03 02:59:04 +08:00
for ( auto & path : crit_paths ) {
const ClockEvent & a = path . first . start ;
const ClockEvent & b = path . first . end ;
if ( a . clock = = b . clock & & a . clock ! = ctx - > id ( " $async$ " ) )
continue ;
xclock_paths . push_back ( path . first ) ;
}
2018-11-03 00:56:53 +08:00
if ( clock_reports . empty ( ) ) {
log_warning ( " No clocks found in design " ) ;
2018-11-03 02:59:04 +08:00
}
std : : sort ( xclock_paths . begin ( ) , xclock_paths . end ( ) , [ ctx ] ( const ClockPair & a , const ClockPair & b ) {
if ( a . start . clock . str ( ctx ) < b . start . clock . str ( ctx ) )
return true ;
if ( a . start . clock . str ( ctx ) > b . start . clock . str ( ctx ) )
return false ;
if ( a . start . edge < b . start . edge )
return true ;
if ( a . start . edge > b . start . edge )
return false ;
if ( a . end . clock . str ( ctx ) < b . end . clock . str ( ctx ) )
return true ;
if ( a . end . clock . str ( ctx ) > b . end . clock . str ( ctx ) )
return false ;
if ( a . end . edge < b . end . edge )
return true ;
return false ;
} ) ;
}
if ( print_path ) {
2018-11-03 22:09:27 +08:00
auto print_path_report = [ ctx ] ( ClockPair & clocks , PortRefVector & crit_path ) {
2018-11-17 00:24:06 +08:00
delay_t total = 0 , logic_total = 0 , route_total = 0 ;
2018-11-03 02:59:04 +08:00
auto & front = crit_path . front ( ) ;
auto & front_port = front - > cell - > ports . at ( front - > port ) ;
auto & front_driver = front_port . net - > driver ;
int port_clocks ;
auto portClass = ctx - > getPortTimingClass ( front_driver . cell , front_driver . port , port_clocks ) ;
IdString last_port = front_driver . port ;
2018-12-13 20:10:18 +08:00
int clock_start = - 1 ;
2018-11-03 02:59:04 +08:00
if ( portClass = = TMG_REGISTER_OUTPUT ) {
2018-11-03 00:56:53 +08:00
for ( int i = 0 ; i < port_clocks ; i + + ) {
TimingClockingInfo clockInfo = ctx - > getPortClockingInfo ( front_driver . cell , front_driver . port , i ) ;
const NetInfo * clknet = get_net_or_empty ( front_driver . cell , clockInfo . clock_port ) ;
2018-11-03 02:59:04 +08:00
if ( clknet ! = nullptr & & clknet - > name = = clocks . start . clock & &
clockInfo . edge = = clocks . start . edge ) {
2018-11-04 22:26:16 +08:00
last_port = clockInfo . clock_port ;
2018-12-13 20:10:18 +08:00
clock_start = i ;
2018-11-16 20:59:27 +08:00
break ;
2018-11-03 00:56:53 +08:00
}
}
2018-11-03 01:26:14 +08:00
}
2018-11-14 06:14:51 +08:00
log_info ( " curr total \n " ) ;
2018-11-03 02:59:04 +08:00
for ( auto sink : crit_path ) {
auto sink_cell = sink - > cell ;
auto & port = sink_cell - > ports . at ( sink - > port ) ;
auto net = port . net ;
auto & driver = net - > driver ;
auto driver_cell = driver . cell ;
DelayInfo comb_delay ;
2018-12-13 20:10:18 +08:00
if ( clock_start ! = - 1 ) {
auto clockInfo = ctx - > getPortClockingInfo ( driver_cell , driver . port , clock_start ) ;
comb_delay = clockInfo . clockToQ ;
clock_start = - 1 ;
} else if ( last_port = = driver . port ) {
2018-11-03 02:59:04 +08:00
// Case where we start with a STARTPOINT etc
2018-11-04 22:03:33 +08:00
comb_delay = ctx - > getDelayFromNS ( 0 ) ;
2018-11-17 00:24:06 +08:00
} else {
2018-12-13 20:10:18 +08:00
ctx - > getCellDelay ( driver_cell , last_port , driver . port , comb_delay ) ;
2018-11-03 02:59:04 +08:00
}
total + = comb_delay . maxDelay ( ) ;
2018-11-17 00:24:06 +08:00
logic_total + = comb_delay . maxDelay ( ) ;
2018-11-03 22:09:27 +08:00
log_info ( " %4.1f %4.1f Source %s.%s \n " , ctx - > getDelayNS ( comb_delay . maxDelay ( ) ) , ctx - > getDelayNS ( total ) ,
driver_cell - > name . c_str ( ctx ) , driver . port . c_str ( ctx ) ) ;
2018-11-03 02:59:04 +08:00
auto net_delay = ctx - > getNetinfoRouteDelay ( net , * sink ) ;
total + = net_delay ;
2018-11-17 00:24:06 +08:00
route_total + = net_delay ;
2018-11-03 02:59:04 +08:00
auto driver_loc = ctx - > getBelLocation ( driver_cell - > bel ) ;
auto sink_loc = ctx - > getBelLocation ( sink_cell - > bel ) ;
log_info ( " %4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d) \n " , ctx - > getDelayNS ( net_delay ) ,
2018-11-03 22:09:27 +08:00
ctx - > getDelayNS ( total ) , net - > name . c_str ( ctx ) , ctx - > getDelayNS ( sink - > budget ) , driver_loc . x ,
driver_loc . y , sink_loc . x , sink_loc . y ) ;
2018-11-14 08:32:06 +08:00
log_info ( " Sink %s.%s \n " , sink_cell - > name . c_str ( ctx ) , sink - > port . c_str ( ctx ) ) ;
if ( ctx - > verbose ) {
auto driver_wire = ctx - > getNetinfoSourceWire ( net ) ;
auto sink_wire = ctx - > getNetinfoSinkWire ( net , * sink ) ;
log_info ( " prediction: %f ns estimate: %f ns \n " ,
2018-11-16 21:25:51 +08:00
ctx - > getDelayNS ( ctx - > predictDelay ( net , * sink ) ) ,
ctx - > getDelayNS ( ctx - > estimateDelay ( driver_wire , sink_wire ) ) ) ;
2018-11-14 08:32:06 +08:00
auto cursor = sink_wire ;
delay_t delay ;
while ( driver_wire ! = cursor ) {
auto it = net - > wires . find ( cursor ) ;
assert ( it ! = net - > wires . end ( ) ) ;
auto pip = it - > second . pip ;
NPNR_ASSERT ( pip ! = PipId ( ) ) ;
delay = ctx - > getPipDelay ( pip ) . maxDelay ( ) ;
2018-11-16 21:25:51 +08:00
log_info ( " %1.3f %s \n " , ctx - > getDelayNS ( delay ) ,
ctx - > getPipName ( pip ) . c_str ( ctx ) ) ;
2018-11-14 08:32:06 +08:00
cursor = ctx - > getPipSrcWire ( pip ) ;
}
}
2018-11-03 02:59:04 +08:00
last_port = sink - > port ;
2018-08-01 17:23:11 +08:00
}
2018-11-16 20:59:27 +08:00
int clockCount = 0 ;
auto sinkClass = ctx - > getPortTimingClass ( crit_path . back ( ) - > cell , crit_path . back ( ) - > port , clockCount ) ;
if ( sinkClass = = TMG_REGISTER_INPUT & & clockCount > 0 ) {
auto sinkClockInfo = ctx - > getPortClockingInfo ( crit_path . back ( ) - > cell , crit_path . back ( ) - > port , 0 ) ;
delay_t setup = sinkClockInfo . setup . maxDelay ( ) ;
total + = setup ;
2018-11-17 00:24:06 +08:00
logic_total + = setup ;
2018-11-16 20:59:27 +08:00
log_info ( " %4.1f %4.1f Setup %s.%s \n " , ctx - > getDelayNS ( setup ) , ctx - > getDelayNS ( total ) ,
crit_path . back ( ) - > cell - > name . c_str ( ctx ) , crit_path . back ( ) - > port . c_str ( ctx ) ) ;
}
2018-11-17 00:24:06 +08:00
log_info ( " %.1f ns logic, %.1f ns routing \n " , ctx - > getDelayNS ( logic_total ) , ctx - > getDelayNS ( route_total ) ) ;
2018-11-03 02:59:04 +08:00
} ;
for ( auto & clock : clock_reports ) {
log_break ( ) ;
2018-11-16 21:25:51 +08:00
std : : string start =
clock . second . first . start . edge = = FALLING_EDGE ? std : : string ( " negedge " ) : std : : string ( " posedge " ) ;
std : : string end =
clock . second . first . end . edge = = FALLING_EDGE ? std : : string ( " negedge " ) : std : : string ( " posedge " ) ;
log_info ( " Critical path report for clock '%s' (%s -> %s): \n " , clock . first . c_str ( ctx ) , start . c_str ( ) ,
end . c_str ( ) ) ;
2018-11-03 02:59:04 +08:00
auto & crit_path = clock . second . second . ports ;
print_path_report ( clock . second . first , crit_path ) ;
}
for ( auto & xclock : xclock_paths ) {
2018-11-03 01:26:14 +08:00
log_break ( ) ;
2018-11-03 02:59:04 +08:00
std : : string start = format_event ( xclock . start ) ;
std : : string end = format_event ( xclock . end ) ;
log_info ( " Critical path report for cross-domain path '%s' -> '%s': \n " , start . c_str ( ) , end . c_str ( ) ) ;
auto & crit_path = crit_paths . at ( xclock ) . ports ;
print_path_report ( xclock , crit_path ) ;
2018-07-26 13:10:26 +08:00
}
}
2018-11-03 02:59:04 +08:00
if ( print_fmax ) {
log_break ( ) ;
2018-11-15 10:27:43 +08:00
unsigned max_width = 0 ;
for ( auto & clock : clock_reports )
max_width = std : : max < unsigned > ( max_width , clock . first . str ( ctx ) . size ( ) ) ;
2018-11-03 02:59:04 +08:00
for ( auto & clock : clock_reports ) {
2018-11-15 10:27:43 +08:00
const auto & clock_name = clock . first . str ( ctx ) ;
const int width = max_width - clock_name . size ( ) ;
2018-11-22 01:13:53 +08:00
float target = ctx - > target_freq / 1e6 ;
if ( ctx - > nets . at ( clock . first ) - > clkconstr )
target = 1000 / ctx - > getDelayNS ( ctx - > nets . at ( clock . first ) - > clkconstr - > period . minDelay ( ) ) ;
bool passed = target < clock_fmax [ clock . first ] ;
if ( ! warn_on_failure | | passed )
2018-11-16 21:25:51 +08:00
log_info ( " Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz) \n " , width , " " ,
2018-11-22 01:13:53 +08:00
clock_name . c_str ( ) , clock_fmax [ clock . first ] , passed ? " PASS " : " FAIL " , target ) ;
else
2018-11-26 17:22:42 +08:00
log_nonfatal_error ( " Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz) \n " , width , " " ,
2018-11-26 17:37:39 +08:00
clock_name . c_str ( ) , clock_fmax [ clock . first ] , passed ? " PASS " : " FAIL " , target ) ;
2018-11-03 02:59:04 +08:00
}
2018-11-14 16:46:10 +08:00
for ( auto & eclock : empty_clocks ) {
if ( eclock ! = ctx - > id ( " $async$ " ) )
log_info ( " Clock '%s' has no interior paths \n " , eclock . c_str ( ctx ) ) ;
}
2018-11-03 02:59:04 +08:00
log_break ( ) ;
int start_field_width = 0 , end_field_width = 0 ;
for ( auto & xclock : xclock_paths ) {
start_field_width = std : : max ( ( int ) format_event ( xclock . start ) . length ( ) , start_field_width ) ;
end_field_width = std : : max ( ( int ) format_event ( xclock . end ) . length ( ) , end_field_width ) ;
}
for ( auto & xclock : xclock_paths ) {
const ClockEvent & a = xclock . start ;
const ClockEvent & b = xclock . end ;
auto & path = crit_paths . at ( xclock ) ;
auto ev_a = format_event ( a , start_field_width ) , ev_b = format_event ( b , end_field_width ) ;
log_info ( " Max delay %s -> %s: %0.02f ns \n " , ev_a . c_str ( ) , ev_b . c_str ( ) , ctx - > getDelayNS ( path . path_delay ) ) ;
}
log_break ( ) ;
}
2018-08-04 13:39:25 +08:00
2018-08-05 22:36:35 +08:00
if ( print_histogram & & slack_histogram . size ( ) > 0 ) {
2018-08-23 00:24:30 +08:00
unsigned num_bins = 20 ;
2018-08-04 14:39:42 +08:00
unsigned bar_width = 60 ;
2018-08-05 09:54:23 +08:00
auto min_slack = slack_histogram . begin ( ) - > first ;
auto max_slack = slack_histogram . rbegin ( ) - > first ;
2018-08-23 00:24:30 +08:00
auto bin_size = std : : max ( 1u , ( max_slack - min_slack ) / num_bins ) ;
num_bins = std : : min ( ( max_slack - min_slack ) / bin_size , num_bins ) + 1 ;
std : : vector < unsigned > bins ( num_bins ) ;
2018-08-04 14:39:42 +08:00
unsigned max_freq = 0 ;
2018-08-04 14:42:25 +08:00
for ( const auto & i : slack_histogram ) {
auto & bin = bins [ ( i . first - min_slack ) / bin_size ] ;
2018-08-04 14:39:42 +08:00
bin + = i . second ;
max_freq = std : : max ( max_freq , bin ) ;
}
bar_width = std : : min ( bar_width , max_freq ) ;
log_break ( ) ;
log_info ( " Slack histogram: \n " ) ;
log_info ( " legend: * represents %d endpoint(s) \n " , max_freq / bar_width ) ;
2018-08-06 22:29:42 +08:00
log_info ( " + represents [1,%d) endpoint(s) \n " , max_freq / bar_width ) ;
2018-08-23 00:24:30 +08:00
for ( unsigned i = 0 ; i < num_bins ; + + i )
2018-08-06 22:29:42 +08:00
log_info ( " [%6d, %6d) |%s%c \n " , min_slack + bin_size * i , min_slack + bin_size * ( i + 1 ) ,
std : : string ( bins [ i ] * bar_width / max_freq , ' * ' ) . c_str ( ) ,
( bins [ i ] * bar_width ) % max_freq > 0 ? ' + ' : ' ' ) ;
2018-08-04 14:39:42 +08:00
}
2018-07-26 09:21:39 +08:00
}
2018-12-02 21:15:39 +08:00
void get_criticalities ( Context * ctx , NetCriticalityMap * net_crit )
{
2018-12-01 21:43:12 +08:00
CriticalPathMap crit_paths ;
net_crit - > clear ( ) ;
Timing timing ( ctx , true , true , & crit_paths , nullptr , net_crit ) ;
timing . walk_paths ( ) ;
}
2018-06-20 17:53:49 +08:00
NEXTPNR_NAMESPACE_END