The previous advice on how to discover the hold and setup times to use in out of context synthesis has been to extract the values from generated timing reports. This is error prone and the timing report for the critical path may not be the path with the precise figures required. The TCL commands required to get the correct timing reports have been provided but still required manual parsing of the text and copy & paste to a constraints file. How far can we go in using TCL to extract the correct values?
- Setup Assumptions
- Basic Example
- More Realistic Example
- Setup & Hold Time Extraction
- Clocks Checks
- Port Constraint Checks
- Running Verification Checks
- Out of Context Synthesis Constraints Construction Process
- Conclusions
- References
It is possible to extract timing data from the objects in your design after synthesis using timing_arc commands. This means is it possible to find the hold and setup times required for out of context synthesis that avoid timing errors on the boundary without making them more relaxed than is healthy for constraining. But as the values are only available after synthesis, it is not possible to use this method to constraint the design initially. Therefore, they can only be used for verification after at least one synthesis build cycle.
Setup Assumptions

- Inputs and outputs are registered;
- Inputs have known logic to drive inside the component, and
- Outputs drive unknown logic outside the component.
- We can't extract hold times from the unknown sequential primitives external to the component feeding the component's inputs.
- We can extract known setup and hold times from sequential primitives driven by the inputs.
- We can only guess at the setup and hold times on sequential primitives driven by the outputs.
- But we can apply representative figures to the inputs and outputs based on the values extracted for the sequential primitives driven by the inputs.
- We could tailor each input's setup and hold times to the maximum of each clocked primitive driven, but
- For simplicity of construction we will take the maximum of each over all inputs, and
- Apply the same values to all outputs as our best guess.
Each sequential primitive can have quite different values for setup and hold times. So for simplicity, and because we're mainly worried about giving sensible values for the boundary, we'll extract the values from sequential primitives fed by the inputs only.
Basic Example
Here is a really basic example for extracting these values using the properties in each timing arc. Note each pin and cell has many timing arcs, and the correct ones have to be selected using both a choice of pin or pins and choice of property. From initial experimentation looking for values that match those determined manually, and from the value types we ought to expect to find based the definition in the original Out of Context Synthesis blog, the correct timing arcs have been located and the property names of the timing values identified. Hold time requires 'min' delays, setup time requires 'max' delays, and the out of context synthesis blog determined than we should only use the 'slow' times for both minimum and maximum input and output delays.
get_property DELAY_SLOW_MIN_RISE [get_timing_arcs -to $pin -filter {TYPE == "hold"}]
get_property DELAY_SLOW_MAX_RISE [get_timing_arcs -to $pin -filter {TYPE == "setup"}]
Hence we have the initial insight on how to extract the required values. However there is a twist for outputs. Each design output is connected to a pin on the other side of the cell from the pin we are required to interogate. So firstly we have to "hop over" the cell from output to inputs. Secondly we have to take care with our choice of input pin(s). We typically want the D input, but may also want to check the other inputs, often the chip enable pin CE. The code below demonstrates how we need to traverse the design. It omits the reset pins from the equation as they are (generally) design inputs, but in due course they may yet be required.
More Realistic Example
We need to get the maximum setup time from all non-trivial inputs, e.g. D and CE and any other significant inputs. Here's some simple TCL code to illustrate:
set l [dict create]
foreach a [get_timing_arcs -quiet -to [get_pins -of_objects [get_cell {...}] -filter {
DIRECTION == IN &&
!IS_CLOCK &&
!IS_CLEAR && !IS_PRESET &&
!IS_TIED
}] -filter {TYPE == "setup"}] {
dict set l [get_property TO_PIN $a] [get_property DELAY_SLOW_MAX_RISE $a]
}
if {[llength $l] > 0} {
lappend dl [::tcl::mathfunc::max {*}[dict values $l]]
}
In addition we may also want to retain knowledge of the primitive from which the value was extracted for explanation. This means dealing with a list of pairs {delay primitive} rather than just a list of delays.
Setup & Hold Time Extraction
The TCL code is not hideous to reverse engineer, but to provide some structure to the plan here's some pseudo code.
# Get maximum delay for each port set port_delays [list] for port in {input_ports} { set ts 0 set th 0 for net in {fanout of $port} { set setup_time [get_delay from setup timing_arc of $net] if ($setup_time > $ts) { set ts $setup_time } set hold_time [get_delay from hold timing_arc of $net] if ($hold_time > $th) { set th $hold_time } } append $port_delays [list $port $ts $th] }
The TCL scripting requires significant care with verification against real netlists for unexpected errors from any assumptions. The code below adds the diagnostics for providing the primitive from which the hold and setup times were extracted for diagnostics.
# For an input list of pairs: {{delay1 primitive1} {delay2 primitive2} {delay3 primitive3}}
# Return the pair with the largest 'delay' value.
#
# Usage: max_delay {{1 a} {3 c} {2 b}} => {3 c}
#
proc max_delay {delay_list} {
# TCL floating point number range: https://www.tcl.tk/man/tcl8.5/tutorial/Tcl6a.html
set mv -1.0e+300
set ret {}
# {d p} = {delay primitive_type}
foreach pair $delay_list {
set d [lindex $pair 0]
set p [lindex $pair 1]
if {$d > $mv} {
set mv $d
set ret [list $d $p]
}
}
return $ret
}
# For input ports only, fetch the maximum both the setup and hold times of a pin in the fanout
# of each port. Also provide the primitive as that might explain any surprises.
#
# Usage: get_setup_hold_times [all_inputs]
#
# Returns: A dictionary keyed on 'setup' or 'hold', of dictionary of pairs
# {input_port {details}, input_port {details}...}
# where 'details' is a list of the form
# {hold_time primitive_type}.
#
# For example:
#
# setup {
# # input_port {hold_time primitive_type}
# m_axis_ready {0.108 REGISTER.SDR.FDCE}
# {m_node_vector[0][major_type][0]} {0.108 REGISTER.SDR.FDRE}
# {m_node_vector[0][major_type][10]} {0.108 REGISTER.SDR.FDRE}
# }
# hold {
# # input_port {hold_time primitive_type}
# m_axis_ready {0.108 REGISTER.SDR.FDCE}
# {m_node_vector[0][major_type][0]} {0.108 REGISTER.SDR.FDRE}
# {m_node_vector[0][major_type][10]} {0.108 REGISTER.SDR.FDRE}
# }
#
proc get_setup_hold_times {ports} {
set setupdelays [dict create]
set holddelays [dict create]
foreach p $ports {
# Exclude asynchronous resets pins and any that are tied to GND or VCC
set fo [filter [all_fanout -quiet -endpoints_only -flat $p] -filter {!IS_CLOCK && !IS_CLEAR && !IS_PRESET && !IS_TIED}]
if {[llength $fo] > 0} {
set sdl {}
set hdl {}
foreach f $fo {
set c [get_cells -of_objects $f]
if {[get_property IS_SEQUENTIAL $c]} {
lappend sdl [list \
[get_property DELAY_SLOW_MIN_RISE [get_timing_arcs -to $f -filter {TYPE == "setup"}]] \
[get_property PRIMITIVE_TYPE $c] \
]
lappend hdl [list \
[get_property DELAY_SLOW_MIN_RISE [get_timing_arcs -to $f -filter {TYPE == "hold"}]] \
[get_property PRIMITIVE_TYPE $c] \
]
# Highlights an issue with 'all_fanout -endpoints_only' above
# } else {
# puts "WARNING in '[lindex [info level 0] 0]': '$f' given as a timing endpoint when it is not sequential."
}
}
# All values in the list tend to be the same
dict set setupdelays $p [max_delay $sdl]
dict set holddelays $p [max_delay $hdl]
}
}
set portdelays [dict create]
dict set portdelays setup $setupdelays
dict set portdelays hold $holddelays
return $portdelays
}
# Verify the hold and setup times supplied at parameters match the synthesised design's expected hold
# and setup times. Note this check can only be done *after* synthesis when the timing arcs are
# available, hence this is a check after the fact rather than a value extraction for constraints before
# synthesis.
#
# NB. Requires a synthesised design to be open. Call 'check_ooc_setup' instead if not.
#
# Usage: check_setup_hold_times $tsus $ths 1
#
# Return: the number of warnings
#
proc check_setup_hold_times {setup_time hold_time {verbose 0} {design synth_1}} {
set warn 0
set times [get_setup_hold_times [all_inputs]]
set setup_design [max_delay [dict values [dict get $times setup]]]
set hold_design [max_delay [dict values [dict get $times hold]]]
# Delay
set sdd [lindex $setup_design 0]
# Primitive
set sdp [lindex $setup_design 1]
# Delay
set hdd [lindex $hold_design 0]
# Primitive
set hdp [lindex $hold_design 1]
if {$sdd != $setup_time} {
puts "WARNING in '[lindex [info level 0] 0]': Specified setup time '$setup_time ns' does not match output ports' maximum setup time of '$sdd ns' on a '$sdp' primtive."
incr warn
} else {
if {$verbose} {
puts "INFO in '[lindex [info level 0] 0]': Specified setup time matches output ports' maximum setup time of '$sdd ns' on a '$sdp' primtive."
}
}
if {$hdd != $hold_time} {
puts "WARNING in '[lindex [info level 0] 0]': Specified hold time '$hold_time ns' does not match input ports' maximum hold time of '$hdd ns' on a '$hdp' primtive."
incr warn
} else {
if {$verbose} {
puts "INFO in '[lindex [info level 0] 0]': Specified hold time matches input ports' maximum hold time of '$hdd ns' on a '$hdp' primtive."
}
}
return $warn
}
A max_delay function is used to reduce a list of {delay primitive} pairs down to a single item with the maximum delay. The essential entry points are get_setup_hold_time [all_inputs]. This function relies on having a synthesised design open. 'check_setup_hold_times $ths $tsus 1' can then be used to verify both $ths and $tsus, the slow process hold and setup times respectively.
Clocks Checks
These two functions are much more trivial and provided to verify that all clocks have been defined.
# Each of these results drives a clock at a sequential primitive and hence must have a constraint to set up a
# clock, e.g. 'create_clock'. This function provides a simple design check.
#
# Usage: design_clock_pins => { user_clks[user_clk_375] sys_ctrl_clk }
#
proc design_clock_pins {} {
return [all_fanin -startpoints_only -flat [get_pins -of_objects [get_cells -hier -filter {IS_SEQUENTIAL}] -filter {IS_CLOCK}]]
}
# Perform a simple check that all clocks have been defined for this design.
# Requires a design to be open, elaborated at least.
#
# Usage: check_design_clocks
#
proc check_design_clocks {} {
set clkports [design_clock_pins]
foreach clk $clkports {
set clk_name [get_clocks -quiet -of_objects $clk]
if {[llength $clk_name] > 0} {
puts "INFO in '[lindex [info level 0] 0]': Clock port '[get_property SOURCE_PINS $clk_name]' has clock name '$clk_name' with period '[get_property PERIOD $clk_name] ns'."
} else {
puts "WARNING in '[lindex [info level 0] 0]': '$clk' is used to drive a clock without a clock definition."
}
}
}
The design_clock_pins function finds all input ports driving a clock pin and check_design_clocks verifies this pins have defined clock constraints.
Port Constraint Checks
It turns out it is possible to check that input and output delay constraints have been applied using:
get_property INPUT_DELAY [get_timing_paths -from $port]
get_property OUTPUT_DELAY [get_timing_paths -to $port]
However, the results for TCL function get_timing_paths are only available after synthesis. This means it is not possible to check the constraints have been applied during constraints setup above, and there are no get_input_delay nor get_output_delay functions. This is limiting because we would be able to check if a manual constraint already existed before printing a warning message about not being able to automatically apply one. As a result, each port for which we cannot automatically add a constraint is printed as a warning even if a manual constraint has already been provided. However, after synthesis we can at least verify all ports have been constrained. This is not done routinely as part of each OOC synthesis run, but manually as part of checking correct setup initially (with a synthesised design open).
# Verify the input and output constraints for each port in the design. This must be run post
# synthesis, i.e. not on an elaborated design.
#
# Usage: check_port_constraints 1
#
# Return: the number of warnings
#
# Sample output to the console:
#
# INFO in 'check_port_constraints': Input port 'sys_ctrl_aresetn' has delay set to 0.152 ns.
# INFO in 'check_port_constraints': Input port 'sys_data_aresetn' has delay set to 0.152 ns.
# INFO in 'check_port_constraints': Output port 'irq' has delay set to 0.439 ns.
# INFO in 'check_port_constraints': Output port 'm_axi4_req[araddr][0]' has delay set to 0.439 ns.
# INFO in 'check_port_constraints': Output port 'm_axi4_req[araddr][10]' has delay set to 0.439 ns.
#
proc check_port_constraints {{verbose 0}} {
set warn 0
# Cannot be a global as it does not exist as the time this function is called.
set script_name "auto_constrain_lib.tcl"
puts "--- Start port constraints verification by $script_name ---"
set input_ports [get_clock_for_input_ports [all_inputs]]
dict for {port data} $input_ports {
set dset [get_property INPUT_DELAY [get_timing_paths -from $port]]
if {$dset != ""} {
if {$verbose} {
puts "INFO in '[lindex [info level 0] 0]': Input port '$port' has delay set to [get_property INPUT_DELAY [get_timing_paths -from $port]] ns."
}
} else {
puts "WARNING in '[lindex [info level 0] 0]': Input port '$port' has no delay set."
incr warn
}
}
set output_ports [get_clock_for_output_ports [all_outputs]]
dict for {port data} $output_ports {
set dset [get_property OUTPUT_DELAY [get_timing_paths -to $port]]
if {$dset != ""} {
if {$verbose} {
puts "INFO in '[lindex [info level 0] 0]': Output port '$port' has delay set to [get_property OUTPUT_DELAY [get_timing_paths -to $port]] ns."
}
} else {
puts "WARNING in '[lindex [info level 0] 0]': Output port '$port' has no delay set."
incr warn
}
}
if {$warn == 0} {
puts "INFO in '[lindex [info level 0] 0]': No missing constraints found."
}
puts "---- End port constraints verification by $script_name ----"
return $warn
}
Running Verification Checks
There are now three forms of verification, setup & hold times, clocks and port constraints, two of which require a synthesised design to be open. The following code ensures that a design is open for checks to be performed and performs all three checks. open_synth_design will re-use an open result or perform the synthesis if it is out of date. check_ooc_setup performs each of the three checks on the now open design and reports the number of warnings. This is a project mode function hence expects you will perform the checks manually rather than via a batch-based build. This is appropriate since once the values have been checked they are written into the constraints and only need to be re-verified when changes are made affecting any primitives the inputs drive and primitives driving the outputs.
# Check we have the sythesised design open, or perform synthesis. Only then is it
# possible to perform post synthesis checks for OOC setup.
#
# Usage: open_synth_design
#
# Return: the number of warnings
#
proc open_synth_design {{synth synth_1} {jobs 6}} {
set d [current_design -quiet]
set synth_run [get_runs $synth]
set must_refesh [get_property NEEDS_REFRESH $synth_run]
if {[llength $d] > 0} {
if {![string equal [lindex $d 0] $synth]} {
puts "Closing design [lindex $d 0] as it is not a synthesis run."
close_design
} elseif {$must_refesh} {
puts "Closing design [lindex $d 0] at it is out of date."
close_design
}
}
if {$must_refesh || [string equal [get_property PROGRESS $synth_run] "0%"]} {
reset_run $synth
launch_runs $synth -jobs $jobs
wait_on_run $synth
}
set d [current_design -quiet]
if {[llength $d] == 0} {
open_run $synth -name $synth
}
}
# Perform OOC synthesis setup checks. This will open a synthesised design if not already open.
#
# Usage: check_ooc_setup $tsus $ths 1
#
proc check_ooc_setup {tsus ths {verbose 0}} {
set warn 0
open_synth_design
# Open a schematic of the basic design - The created window distracts from the TCL console where the result is printed.
#report_timing_summary -delay_type min_max -report_unconstrained -check_timing_verbose -max_paths 10 -input_pins -routable_nets -name timing_synth
# Check the TCL console for the printed results.
incr warn [check_setup_hold_times $tsus $ths $verbose]
incr warn [check_design_clocks $verbose]
incr warn [check_port_constraints $verbose]
puts "INFO in '[lindex [info level 0] 0]': Number of warnings to address: $warn."
return $warn
}
Out of Context Synthesis Constraints Construction Process
Given the practical constraints that we cannot extract setup and hold times or check if input and output delay constraints have been applied pre-synthesis, the process is as follows:

Start with some nominal values for setup and hold times based on previous experience. Run OOC synthesis with automatically derived constraints. Either read the console transcript to see which ports could not be automatically constrained or check the synthesis timing report to see which inputs remain unconstrained. Also check the values of setup and hold times to use from this initial run. This is all achieved by running check_ooc_setup. Then add the missing constraints manually to your constraints file, and amend the setup and hold times. Now re-run OOC synthesis and repeat the process until all check_ooc_setup checks pass. From now on there is no need to re-run the checks, just work on the VHDL to achieve timing closure.
Conclusions
The extraction of timing data was previously a manual step. Whilst the generation of the correct reports could be automated, they still needed to be inspected and the values extracted by copy & paste. This solution automates the extraction of these values with the discovery of the TCL get_timing_arcs comand, and an investigation into the properties it offers. Another formation of the solution could be to have a function that writes out the input and output delay constraints as text, tailored to each port with their own setup and hold times, for copy & paste to the constraints file, instead of just applying the maximum value uniformly to all ports.
This blog should be taken together with two others, Specifying Boundary Timing Constraints in Vivado and Extracting Setup and Hold Times from Devices for Out of Context Synthesis. The three together define how to constrain a sub-design not connecting to device pins, firstly how to calculate the delay values to use for input and output constraints, then how to automate their application to pins with a variety of clock domains, and finally how to extract the fundamental setup and hold times to be used in the original calculations.