One of the annoyances with the method of Specifying Boundary Timing Constraints in Vivado is that each input and output (albeit in bus-based blocks) needs to be treated with a clock domain and a delay. For designs with multiple clocks and many ports this becomes cumbersome, and the information to automate this is present in the elaborated design. This blog covers how to extract the clock domains in an automatic way, and then apply the constraints to a design, removing a tedious step in the process.
- Problem Specification
- Simple Example
- Asynchronous Nets
- Tricky Cases
- LUTRAM
- XPM CDC
- Dual Clocked Primitives
- Solution
- Extraction
- Validation
- Constraints Derivation
- Template
- Conclusions
- References
Problem Specification
The aim is to construct two TCL scripts to run in Vivado as an unmanaged constraints files:
- A library script to be re-used, read_xdc -unmanaged -mode out_of_context out_of_context_synth_lib.tcl
- A script customisable to the component with minimum changes, read_xdc -unmanaged -mode ooc.tcl

The scripts between them must setup the input and output delay constraints required for the boundary conditions such that static timing analysis does not report errors on the boundary and instead highlights issues within the component. The delay values are already specified by the previous blog and the form of the constraints currently manually added for each port, the missing piece of information is the clock domain to use for each constraint.
The diagram above illustrates the plan with an example. The correct answer for this example would be:
- Input port A is associated with clock domain clk1.
- Input port B is associated with clock domain clk2.
- Input port C is assumed to be a false path since it feeds a synchroniser (CDC).
- Outputs X & Z are associated with clk1.
- Output Y is associated with clk2.
Note the solution settles for false path rather than maximum delay constraints on asynchronous inputs. This choice is made because using set_max_delay constraints caused a different issue with partially specified input constraints. The solution probably requires both minimum and maximum input delays to be specified and different, but as our technique has shown, both minimum and maximum values need to be set the same. The minimum and maximum pair of constraints when set explicitly to the same value then get collapsed into one with neither -min nor -max and the partially specified input delay warning returns. As Xilinx XPM use false paths for their constraints, this solution opts for that simpler method, leaving the component integration stage to decide on whole image constraints.
Simple Example

For the design above, the solution prints the textual output given below.
--- Start automatically derived constraints by out_of_context_synth_lib.tcl --- set_input_delay -clock clk_src1 0.261 flags_src1[0] set_input_delay -clock clk_src1 0.261 flags_src1[1] set_input_delay -clock clk_src2 0.261 flags_src2[0] set_input_delay -clock clk_src2 0.261 flags_src2[1] set_input_delay -clock clk_dest 0.261 reset_dest set_input_delay -clock clk_src1 0.261 reset_src1 set_input_delay -clock clk_src2 0.261 reset_src2 set_output_delay -clock clk_dest 0.037 flags_out[0] set_output_delay -clock clk_dest 0.037 flags_out[1] set_output_delay -clock clk_dest 0.037 flags_out[2] set_output_delay -clock clk_dest 0.037 flags_out[3] ---- End automatically derived constraints by out_of_context_synth_lib.tcl ----
Note that the output is not formatted in an immediately useable way. As an example of the translation:
set_input_delay -clock clk_src1 0.261 flags_src1[0]
would have to be reformatted as:
set_input_delay -clock [get_clocks {clk_src1}] 0.261 [get_ports {flags_src1[0]}]
The TCL printing does not embellish its output with the additional TCL commands required for correct "copy & paste" to the TCL console, and no attempt has been made to add it since the output is just a record of the constraints applied to the design.
Asynchronous Nets
One observation during the use of real examples was the need to manage any port feeding into a synchroniser usually marked with an ASYNC_REG property, or for that matter any asynchronous pin like a reset such as those with either IS_CLEAR or IS_PRESET pin properties. Therefore the extraction of information should mark such nets for later decision making, e.g. with an optional value of "ASYNC" in the TCL list associated with the input port.Tricky Cases
Inevitably there are some more tricky primitives to deal with or work around, here are two already encountered with more yet to be discovered.
LUTRAM
The issue here is that the LUTRAM's CLK pin does not bear the properties the code relies on. This is from a primitive of type CLB.LUTRAM.RAM32M16.
Property Type Read-only Value
CLASS string true pin
DIRECTION enum true IN
HD.ASSIGNED_PPLOCS string* true
HD.TANDEM int false 0
HOLD_SLACK double true needs timing update***
IS_CLEAR bool true 0
IS_CLOCK bool true 0
IS_CONNECTED bool true 1
IS_ENABLE bool true 0
IS_INVERTED bool false 0
IS_LEAF bool true 1
IS_ORIG_PIN bool true 1
IS_PRESET bool true 0
IS_RESET bool true 0
IS_REUSED bool true 0
IS_SET bool true 0
IS_SETRESET bool true 0
IS_TIED bool true 0
IS_WRITE_ENABLE bool true 0
LOGIC_VALUE string true needs timing update***
NAME string true path/U0/inst_fifo_gen/gaxis_fifo.gaxisf.axisf/grf.rf/gntv_or_sync_fifo.mem/gdm.dm_gen.dm/RAM_reg_0_15_56_64/RAME/CLK
PARENT_CELL cell true path/U0/inst_fifo_gen/gaxis_fifo.gaxisf.axisf/grf.rf/gntv_or_sync_fifo.mem/gdm.dm_gen.dm/RAM_reg_0_15_56_64/RAME
PARTITION_ID int true 0
REF_NAME string true RAMD32
REF_PIN_NAME string true CLK
SETUP_SLACK double true needs timing update***
You might have expected the CLK pin to have the IS_CLOCK property set. These are the properties from elaboration and hence present in the RTL view. Thankfully the primitive does have a clock associated with it via the cell properties, so [get_clocks -of_objects [get_cells {path/U0/inst_fifo_gen/gaxis_fifo.gaxisf.axisf/grf.rf/gntv_or_sync_fifo.mem/gdm.dm_gen.dm/RAM_reg_0_15_56_64/RAME}]] will provide a useable clock.
It also happens that the WCLK port on the next level of hierarchy up does have its IS_CLOCK property set, and the CLK pin does inherit the IS_CLOCK property at synthesis.
XPM CDC
The XPM CDC component is shown as an RTL_REG_ASYNC after elaboration. Now typically (according to Xilinx UG912), a CLR pin should have the IS_CLEAR property set to show that it is an asynchronous reset input.

Property Type Read-only Value
CLASS string true pin
DIRECTION enum true IN
HD.ASSIGNED_PPLOCS string* true
HD.TANDEM int false 0
HOLD_SLACK double true needs timing update***
IS_CLEAR bool true 0
IS_CLOCK bool true 0
IS_CONNECTED bool true 1
IS_ENABLE bool true 0
IS_INVERTED bool false 0
IS_LEAF bool true 1
IS_ORIG_PIN bool true 1
IS_PRESET bool true 0
IS_RESET bool true 0
IS_REUSED bool true 0
IS_SET bool true 0
IS_SETRESET bool true 0
IS_TIED bool true 0
IS_WRITE_ENABLE bool true 0
LOGIC_VALUE string true needs timing update***
NAME string true path/xpm_cdc_async_rst_i/arststages_ff_reg[0]/CLR
PARENT_CELL cell true path/xpm_cdc_async_rst_i/arststages_ff_reg[0]
PARTITION_ID int true 0
REF_NAME string true RTL_REG_ASYNC__BREG_1
REF_PIN_NAME string true CLR
SETUP_SLACK double true needs timing update***
After synthesis the property is correctly set. This is an issue because ideally we need the constraints to work on either the elaborated or the synthesised design to avoid TCL code execution errors becoming a distraction.

Property Type Read-only Value
CLASS string true pin
DIRECTION enum true IN
HD.ASSIGNED_PPLOCS string* true
HD.TANDEM int false 0
HOLD_SLACK double true needs timing update***
IS_CLEAR bool true 1
IS_CLOCK bool true 0
IS_CONNECTED bool true 1
IS_ENABLE bool true 0
IS_INVERTED bool false 0
IS_LEAF bool true 1
IS_ORIG_PIN bool true 1
IS_PRESET bool true 0
IS_RESET bool true 0
IS_REUSED bool true 0
IS_SET bool true 0
IS_SETRESET bool true 0
IS_TIED bool true 0
IS_WRITE_ENABLE bool true 0
LOGIC_VALUE string true needs timing update***
NAME string true path/xpm_cdc_async_rst_i/arststages_ff_reg[1]/CLR
PARENT_CELL cell true path/xpm_cdc_async_rst_i/arststages_ff_reg[1]
PARTITION_ID int true 0
REF_NAME string true FDCE
REF_PIN_NAME string true CLR
SETUP_SLACK double true needs timing update***
The present solution is a rather UltraScale specific one, to test for an XPM_CDC property set to ASYNC_RST. Then identify the net as feeding a CLR pin and hence an asynchronous input. The other options here are to ignore it and expect the synthesis tool to know this and hence false path it, or to leave the XPMs to their own constraints. In the case of the latter, the XPM constraints do not specify a false path for this net.
Property Type Read-only Value
ASYNC_REG bool false 1
CLASS string true cell
FILE_NAME string true C:/Xilinx/Vivado/2020.1/data/ip/xpm/xpm_cdc/hdl/xpm_cdc.sv
IS_BLACKBOX bool true 0
IS_BOUNDARY_INST bool true 0
IS_DEBUGGABLE bool true 1
IS_MATCHED bool true 1
IS_ORIG_CELL bool true 1
IS_PRIMITIVE bool true 1
IS_REUSED bool true 0
IS_SEQUENTIAL bool true 1
KEEP string false true
LINE_NUMBER int true 1242
NAME string true path/xpm_cdc_async_rst_i/arststages_ff_reg[1]
PARENT cell true path/xpm_cdc_async_rst_i
PRIMITIVE_COUNT int true 1
PRIMITIVE_GROUP enum true RTL_REGISTER
PRIMITIVE_LEVEL enum true LEAF
PRIMITIVE_SUBGROUP enum true flop
PRIMITIVE_TYPE enum true RTL_REGISTER.flop.RTL_REG
REF_NAME string true RTL_REG_ASYNC__BREG_1
REUSE_STATUS enum true
STATUS enum true UNPLACED
XPM_CDC enum false ASYNC_RST
map_to_module int false 0
Dual Clocked Primitives
BlockRAMs are by their nature dual clocked primitives, so which clock should we use? It turns out there is not reliable way to relate an input pin to a single clock pin. One might consider parsing the pin name to extract an 'A' or 'B' letter, but actually this is not reliable, for example a BlockRAM input pin called ADDRARDADDR has three 'A's, and you need to determine the pin has a prefix ADDR and a suffix also ADDR, by comparison to a pin called ADDRENA. There are no clues in the pin properties either.
The solution employed here is more generic, if the primitive being queried has multiple clocks, report both back from the information extraction process, and the decision making process must then decide what to do. Here it decides to report the problem, propose the additional constraints are added manually to the constraints file and provides suggested constraints to add for each clock so they can be copied, as a time saving measure for the author.
WARNING in 'setup_port_constraints': Input port 'master_axi_com[rdata][0]' has multiple clocks. Clock list: {wr_clk wr_clk} {rd_clk rd_clk}. # Select the best constraint and add manually in OOC constraints, before call to 'setup_port_constraints' # set_input_delay -clock [get_clocks {wr_clk}] 0.148 [get_port {master_axi_com[rdata][0]}] # set_input_delay -clock [get_clocks -of_objects [get_ports {wr_clk}]] 0.148 [get_port {master_axi_com[rdata][0]}] # set_input_delay -clock [get_clocks {rd_clk}] 0.148 [get_port {master_axi_com[rdata][0]}] # set_input_delay -clock [get_clocks -of_objects [get_ports {rd_clk}]] 0.148 [get_port {master_axi_com[rdata][0]}]
Solution
The library code (out_of_context_synth_lib.tcl) execution takes the form of:
- Extraction of net information
- Validation of clock domains
- Derivation of constraints
Each stage is detailed separately in the following sections. The code uses a lot of properties of objects in Vivado, and development has demonstrated how easy it is to make assumptions about how consistently the properties have been applied, or how reliable a method of design navigation is. Therefore, the latest version of the code on GitHub must always be referred to instead of copying the code below without checking and understanding.
Extraction
For inputs, discover the first register they feed. This was the stumbling point in the original abandoned implementation. The missed trick was to filter the port's fanout based on primitive type, covering both RTL and mapped primitives, and changes between device families. E.g. the Kintex family uses different PRIMITIVE_GROUP values to the Zynq family, those being "REGISTER" and "FLOP_LATCH" respectively. This means the code is rather fragile to a change of device family, but hopefully constant over a project's lifetime. The Cell Primitives Table in Xilinx UG912 lists the values that can be expected, but it does not mention "FLOP_REGISTER" being an option.
Two variations of port extraction are used as they are slightly different for inputs (proc get_clock_for_input_ports) and outputs (proc get_clock_for_output_ports). The former needs to deal with feeding into an asynchronous primitive pin. Both refer to the same proc get_clock_port_of_registers to extract the clock from a primitive. The extraction results are returned in the form of a TCL dictionary pairing a port with a list of destinations, each destination itself being a list comprised of {register, clock name, clock pin}. Where possible no approximation or loss of detail is performed on extraction, in order to avoid pre-empting any decision making later.
# port { list {tuples} per net } {flags_in[0]} {{{flags_in_i_reg[0]} clk_src_nm clk_src}} {flags_in[1]} {{{flags_in_i_reg[1]} clk_src_nm clk_src}} reset_dest { {{retime_i/reg_retime_reg[4][3]} clk_dest_nm clk_dest ASYNC} ... {{retime_i/reg_retime_reg[0][0]} clk_dest_nm clk_dest ASYNC} {{flags_out_reg[3]} clk_dest_nm clk_dest} ... {{flags_out_reg[0]} clk_dest_nm clk_dest} }
The ASYNC value is an optional flag used to mark a port driving an asynchronous register pin. This is used as a flag to alter the actions taken for such a port.
# Get the clock source of each cell in the supplied 'cells' list
#
# Usage: get_clock_port [get_selected_objects]
#
# Returns: Each cell listed with its clock name and clock source port
# E.g. {{cell clock_name clock_port} {cell clock_name clock_port} ...}
#
# An exception is made here for primitives with no clocks, where the return format will
# include a list element of the format:
# {cell NOTSET clock_port}
#
proc get_clock_port_of_registers {cells} {
set clklist {}
foreach c $cells {
# Filter out anything that is not a cell, need to cope with both RTL from elaboration and device primitives from synthesis
# See Ref [1] for filter criteria
if {[llength [get_cells -quiet $c -filter {IS_SEQUENTIAL}]] > 0} {
set pins [get_pins -quiet -of_objects $c -filter {IS_CLOCK}]
if {[llength $pins] < 1} {
# E.g. LUTRAM have a CLK pin that do not have their IS_CLOCK property set to 1.
set clksrc [get_clocks -quiet -of_objects $c]
if {[llength $clksrc] > 0} {
lappend clklist [list $c $clksrc [get_property SOURCE_PINS $clksrc]]
} else {
# puts "WARNING in '[lindex [info level 0] 0]': No clock constraint found for cell '$c' of primitive type '[get_property PRIMITIVE_TYPE $c]'."
lappend clklist [list $c NOTSET NOTFOUND]
}
} else {
foreach p $pins {
set clksrc [get_clocks -quiet -of_objects $p]
# This might not return a value, but the pin is connected to a clock port, so trace it back to the origins.
if {[llength $clksrc] > 0} {
set pinsrc [get_property SOURCE_PINS $clksrc]
} else {
set pinsrc [all_fanin -flat -startpoints_only $p]
set clksrc [get_clocks -quiet -of_objects $pinsrc]
}
if {[llength $clksrc] > 0} {
lappend clklist [list $c $clksrc $pinsrc]
} else {
# puts "WARNING in '[lindex [info level 0] 0]': No clock constraint found for pin '$p' of primitive type '[get_property PRIMITIVE_TYPE $c]'."
lappend clklist [list $c NOTSET $pinsrc]
}
}
}
}
}
return $clklist
}
# Returns a dictionary of lists such that each input port is associated with a list
# of destination registers each associated with its clock name and clock input port.
# If the input is asynchronous, then the string ASYNC is appended to the inner list
# after the clock domain and clock pin. This provides warning that the destination
# register's clock domain is not an indicator of the input port's originating clock
# domain.
#
# Usage:
# get_clock_for_input_port [get_ports {port1 port2} -filter {DIRECTION == "IN"}]
# get_clock_for_input_port [all_inputs]
#
# Returns:
# {flags_in[0]} {{{flags_in_i_reg[0]} clk_src_nm clk_src}}
# {flags_in[1]} {{{flags_in_i_reg[1]} clk_src_nm clk_src}}
# {flags_in[2]} {{{flags_in_i_reg[2]} clk_src_nm clk_src}}
# {flags_in[3]} {{{flags_in_i_reg[3]} clk_src_nm clk_src}}
# reset_dest {
# {{retime_i/reg_retime_reg[4][3]} clk_dest_nm clk_dest ASYNC}
# :
# {{retime_i/reg_retime_reg[0][0]} clk_dest_nm clk_dest ASYNC}
# {{flags_out_reg[3]} clk_dest_nm clk_dest}
# :
# {{flags_out_reg[0]} clk_dest_nm clk_dest}
# }
# reset_src {
# {{retime_i/reg_capture_reg[3]} clk_src_nm clk_src}
# :
# {{retime_i/reg_capture_reg[0]} clk_src_nm clk_src}
# {{flags_in_i_reg[3]} clk_src_nm clk_src}
# :
# {{flags_in_i_reg[0]} clk_src_nm clk_src}
# }
#
proc get_clock_for_input_ports {ports} {
set clklist [dict create]
foreach p $ports {
if {[llength [get_clocks -of_objects $p -quiet]] == 0} {
set fo [filter [all_fanout -flat $p] -filter {!IS_CLOCK}]
if {[llength $fo] > 0} {
set reglist {}
foreach f $fo {
# Filter out anything that is not a cell, need to cope with both RTL from elaboration and device primitives from synthesis
# See Ref [1] for filter criteria
set c [get_cells -quiet -of_objects $f -filter {IS_SEQUENTIAL}]
if {[llength $c]} {
# Can return multiple clocks for a single primitive
set regs [get_clock_port_of_registers $c]
foreach r $regs {
if {[llength $r] == 0} {
error "ERROR in '[lindex [info level 0] 0]': 'get_clock_port_of_registers $c' returned no registers for port '$p'."
} else {
set l $r
# Is $f the reset pin to $c or a data pin?
#
# * Data pin to a register with property ASYNC_REG - Mark input ASYNC for false path (later)
# * Cell with XPM_CDC property ASYNC_RST - Mark input ASYNC for false path (later), UltraScale specific,
# no pin properties IS_CLEAR || IS_PRESET yet (argh!)
# * Asynchronous reset pin - Mark input ASYNC for false path (later), e.g. cell name
# RTL_REG_ASYNC, pin properties IS_CLEAR || IS_PRESET
# * Synchronous reset pin - No ASYNC_REG to ensure it remains a timed path, e.g. pin
# properties IS_RESET || IS_SET
#
# Another strategy for cells with XPM_CDC properties (any value) is to mark them (e.g. with an optional "XPM") for
# absolutely no treatment later and allow XPMs to sort themselves out with their own constraints. However the XPM
# constraints are not marking them up themselves. Watch and learn here.
#
# NB. Need to cater for a synchronous reset feeding an ASYNC_REG register, hence the check for both sorts of reset pins.
#
# NB. pins with IS_SETRESET give us problems: Programmable synchronous or asynchronous set/reset. The pin's behavior is
# controlled by an attribute on the block. E.g. The RSTRAMB pin on a RAMB36E2. Ignore for now, as IS_SETRESET is set
# when IS_RESET is also set, so the documentation is not sufficiently complete on this attribute.
#
# if {[llength [get_pins -quiet -of_objects $c -filter {IS_SETRESET}]] > 0} {
# error "ERROR in '[lindex [info level 0] 0]': 'IS_SETRESET' property used on an input pin of '$c', the script cannot handle these."
# }
#
set asyncrsts [get_pins -quiet -of_objects $c -filter {IS_CLEAR || IS_PRESET}]
set syncrsts [get_pins -quiet -of_objects $c -filter {IS_RESET || IS_SET}]
if {(([get_property ASYNC_REG $c] == 1) ||
(([string equal [get_property XPM_CDC $c] "ASYNC_RST"]) && ([string match "*/CLR" $f])) ||
(([llength $asyncrsts] > 0) && ([lsearch -exact $asyncrsts $f] >= 0))) &&
!(([llength $syncrsts] > 0) && ([lsearch -exact $syncrsts $f] >= 0))} {
lappend l {ASYNC}
}
lappend reglist $l
}
}
}
}
dict set clklist $p $reglist
}
}
}
return $clklist
}
# Returns a dictionary of lists such that each output port is associated with a list
# of source registers paired with its clock name and clock input port. NB. There can
# be more than one source register if the output port is not directly registered but
# instead includes combinatorial logic.
#
# Usage:
# get_clock_for_output_ports [get_ports {port1 port2} -filter {DIRECTION == "OUT"}]
# get_clock_for_output_ports [all_outputs]
#
# Returns:
# {flags_out[0]} {{{flags_out_reg[0]} clk_dest_nm clk_dest}}
# {flags_out[1]} {{{flags_out_reg[1]} clk_dest_nm clk_dest}}
# {flags_out[2]} {{{flags_out_reg[2]} clk_dest_nm clk_dest}}
# {flags_out[3]} {{{flags_out_reg[3]} clk_dest_nm clk_dest}}
#
proc get_clock_for_output_ports {ports} {
set clklist [dict create]
foreach p $ports {
set fi [filter [all_fanin -quiet -flat $p] -filter {IS_CLOCK}]
if {[llength $fi] > 0} {
set reglist {}
foreach f $fi {
set c [get_cells -of_objects $f -quiet]
if {[llength $c]} {
lappend reglist {*}[get_clock_port_of_registers $c]
}
}
dict set clklist $p $reglist
}
}
return $clklist
}
Validation
Each port must be associated with precisely one clock, unless it feeds an asynchronous pin. Hence inputs may be associated with 0 or 1 clocks whilst output ports must be associated with exactly 1 clock. When an input is associate with zero clocks it is usually because it is asynchronous to the destination clock domain (as evidenced by the 'ASYNC' flag) and so a false path constraint is provided. When multiple unique clocks are listed for a port, a warning is printed and suggested constraints provided for manual selection and addition to the constraints file. The procedure single_port_unique_clock_domains below can be used to extract the unique clocks per port, hopefully returning precisely one for automatic constraint application.
# For a single port's list of connections, check return a list of unique clock domains.
#
# Parameters:
# port_data - A list of tuples in the format:
# {
# {{flags_out_reg[0]} clk_dest_nm1 clk_pin1}
# {{flags_out_reg[1]} clk_dest_nm1 clk_pin1}
# {{flags_out_reg[2]} clk_dest_nm2 clk_pin2}
# }
#
# Returns: A list of the unique clock names and clock pins, e.g.
# {
# {clk_dest_nm1 clk_pin1}
# {clk_dest_nm2 clk_pin2}
# }
#
# Usage: single_port_unique_clock_domains [dict get [get_clock_for_input_ports [get_ports {m_axi4_com[arready]}]] {m_axi4_com[arready]}]
# => {sys_data_clk sys_data_clk}
#
proc single_port_unique_clock_domains {port_data} {
set clks {}
foreach d $port_data {
if {![string equal [lindex $d 3] "ASYNC"]} {
set clkgrp [lindex $d 1]
set clkpin [lindex $d 2]
set item [list $clkgrp $clkpin]
if {[lsearch -exact $clks $item] < 0} {
lappend clks $item
}
}
}
return $clks
}
Constraints Derivation
For inputs we decide between a set_false_path constraint for any port-pin pairs that are classed as asynchronous or a set_input_delay constraint with the now known clock and the input delay previously calculated for the out of context boundary conditions. For the outputs there's no choice, just the set_output_delay constraint for each port in the dictionary.
# Automatically apply input and output timing constraints for OOC synthesis.
#
# Parameters:
# input_delay - The input delay to use with 'set_input_delay' in ns
# output_delay - The output delay to use with 'set_output_delay' in ns
# verbose - 0 or 1, control echo'ing of constraints to the transcript for
# visibility of execution.
#
# Usage: setup_port_constraints $input_delay $output_delay
#
# Observation: Using 'set_max_delay' instead of 'set_false_path' means that the
# corresponding input port is reported by static timing analysis to be partially
# constrained. To correct this, both a '-min' and a '-max' input delay must be
# set. Except we want them set to the same value for OOC synthesis, and the tool
# collapses the two separate min/max constraints into one constraint and
# continues to warn about the partially constrained input. XPM use false paths.
#
proc setup_port_constraints {input_delay output_delay {verbose 0}} {
# Cannot be a global as it does not exist as the time this function is called.
set script_name "auto_constrain_lib.tcl"
if {$verbose} {
puts "--- Start automatically derived constraints by $script_name ---"
}
dict for {port data} [get_clock_for_input_ports [all_inputs]] {
# 'data' is the list of destination sequential primitives
# Cannot extract any existing input or output delay from a port without 'get_timing_paths',
# which requires a synthesied design, not just elaboration.
# * get_property INPUT_DELAY [get_timing_paths -from $port]
# * get_property OUTPUT_DELAY [get_timing_paths -to $port]
set setopd 0
set ucd [single_port_unique_clock_domains $data]
if {[llength $ucd] == 1} {
foreach d $data {
# d = {register clock_name clock_port ?ASYNC?}
if {[string equal [lindex $d 3] "ASYNC"]} {
# Set up false paths from any ports to each ASYNC pin.
if {$verbose} {
puts "set_false_path -from $port -to [lindex $d 0]"
}
# Don't false path the reset though, that must be timed to the register's clock.
set_false_path -from $port -to [lindex $d 0]
# Alternative is: set_max_delay -datapath_only -from [get_ports $port] -to [lindex $d 0] [get_property PERIOD [get_clocks [lindex $d 1]]]
# See observation above.
} elseif {[string equal [lindex $d 1] "NOTSET"]} {
puts "WARNING in '[lindex [info level 0] 0]': Input port '$port' has unknown clock constraint on pin '[lindex $d 2]'."
} elseif {! $setopd} {
# Only need to set up this constraint the first time
set setopd 1
if {$verbose} {
puts "set_input_delay -clock [get_clocks [lindex $d 1]] $input_delay $port"
}
set_input_delay -clock [get_clocks [lindex $d 1]] $input_delay $port
}
}
} elseif {[llength $data] > 1} {
# if $data contains more then one clock... Provide evidence and call for manual setting
puts "WARNING in '[lindex [info level 0] 0]': Input port '$port' has multiple clocks. Clock list: ${ucd}."
if {$verbose} {
puts "# Select the best constraint and add manually in OOC constraints, before call to '[lindex [info level 0] 0]'"
foreach c $ucd {
if {![string equal [lindex $c 0] "NOTSET"]} {
puts "# set_input_delay -clock \[get_clocks {[lindex $c 0]}\] $input_delay \[get_port {$port}\]"
}
puts "# set_input_delay -clock \[get_clocks -of_objects \[get_ports {[lindex $c 1]}\]\] $input_delay \[get_port {$port}\]"
}
}
} else {
# ([llength $data] == 0) || ($except == "NONE")
puts "WARNING in '[lindex [info level 0] 0]': Input port '$port' has no clocks."
}
}
dict for {port data} [get_clock_for_output_ports [all_outputs]] {
# 'data' is the list of destination sequential primitives
set setopd 0
set ucd [single_port_unique_clock_domains $data]
if {[llength $ucd] == 1} {
foreach d $data {
# d = {register clock_name clock_port ?ASYNC?}
if {[string equal [lindex $d 1] "NOTSET"]} {
puts "WARNING in '[lindex [info level 0] 0]': Output port '$port' has unknown clock constraint on pin '[lindex $d 2]'."
} elseif {! $setopd} {
# Only need to set up this constraint the first time
set setopd 1
if {$verbose} {
puts "set_output_delay -clock [get_clocks [lindex $d 1]] $output_delay $port"
}
set_output_delay -clock [get_clocks [lindex $d 1]] $output_delay $port
}
}
} elseif {[llength $data] > 1} {
# if $data contains more then one clock... Provide evidence and call for manual setting
puts "WARNING in '[lindex [info level 0] 0]': Output port '$port' has multiple clocks. Clock list: ${ucd}."
if {$verbose} {
puts "# Select the best constraint and add manually in OOC constraints, before call to '[lindex [info level 0] 0]'"
foreach c $ucd {
if {![string equal [lindex $c 0] "NOTSET"]} {
puts "# set_output_delay -clock \[get_clocks {[lindex $c 0]}\] $output_delay \[get_port {$port}\]"
}
puts "# set_output_delay -clock \[get_clocks -of_objects \[get_ports {[lindex $c 1]}\]\] $output_delay \[get_port {$port}\]"
}
}
} else {
# ([llength $data] == 0) || ($except == "NONE")
puts "WARNING in '[lindex [info level 0] 0]': Output port '$port' has no clocks."
}
}
if {$verbose} {
puts "---- End automatically derived constraints by $script_name ----"
}
puts "INFO Out of Context Synthesis: Call TCL 'check_ooc_setup \$tsus \$ths' to check the OOC setup."
}
Template
As a result of this work, the OOC constraints file no longer needs to list the input and output delay constraints. It does need to define the clocks used, as these definitions prevent the clock inputs getting any delay constraints of their own. It also needs to list the particular device's setup and hold times which still need to be manually extracted from timing reports. A solution to this remains highly desirable.
# Remove any lingering hang-over results and start again, this seems to become necessary
# when using TCL-based constraints file. NB. the OOC constraint files must be at the top
# of the list in order to be processed first.
reset_timing -quiet
# Clock uncertainty (from a timing report), looks to be device independent
set tcu 0.035
# Extract more precise setup and hold times using the TCL function
# 'check_setup_hold_times $tsus $ths 1' post initial synthesis.
# Kintex-7 Parts: xcku040-ffva1156-2-i and xcku060-ffva1156-2-i
# FDRE Setup Time (Setup_FDRE_C_D) in ns (Slow Process, max delay for Setup times)
set tsus 0.047
# FDRE Hold Time (Hold_FDRE_C_D) in ns (Slow Process, min delay for Hold times)
set ths 0.108
# Zynq Part: xc7z020clg484-1
# FDRE Setup Time (Setup_FDRE_C_D) in ns (Slow Process, max delay for Setup times)
#set tsus 0.169
# FDRE Hold Time (Hold_FDRE_C_D) in ns (Slow Process, min delay for Hold times)
#set ths 0.243
# Choose these:
#
# Extra slack (on hold time), designer's choice
set txs 0.008
# Additional clock uncertainty desired for over constraining the design, set by designer choice
set tcu_add 0.000
create_clock -period 5.000 -name clk_200 [get_ports clk1]
create_clock -period 7.000 -name clk_250 [get_ports clk2]
# Standard timing setup, allocate the device delays into the meaningful variables
#
# https://www.xilinx.com/publications/prod_mktg/club_vivado/presentation-2015/paris/Xilinx-TimingClosure.pdf
# Recommended technique for over-constraining a design (can complain if applied at elaboration,
# but still gets applied to synthesis):
if {[string match "rtl*" [get_design -quiet]]} {
puts "Skipping 'set_clock_uncertainty' command as this is only an elaborated design."
} else {
set_clock_uncertainty -quiet -setup $tcu_add [get_clocks]
}
# Input Hold = Input Setup (slow corner)
set input_delay [expr $ths + $tcu + $txs]
# Output Hold = Output Setup (slow corner)
set output_delay $tsus
# Add manual constraints here for ports where the clock domain cannot be automatically determined.
# Automatically determine the clock domain of each input and output, and assign the appropriate delay.
if {[llength [info procs setup_port_constraints]] == 1} {
setup_port_constraints $input_delay $output_delay 1
} else {
puts "You need to 'source -notrace {/path/to/out_of_context_synth_lib.tcl}' first."
}
Finally, the Vivado project requires the two files to be added to the constraints. The order must be as shown in the image below, and they must precede any other constraints files (XDC or TCL) used. Here the template above has been customised to a file called ooc.tcl.

Conclusions
The determination of different clocks and then writing of the input and output delay constraints was a significant inconvenience involving multiple trials to ensure all have been correctly included for a timing report. This solution is looking viable as a replacement to the error prone specification. Of course for single clock domain designs only two lines were required before (e.g. set_input_delay -clock clk 0.159 [all_inputs] and the corresponding output delay constraint). So this solution really comes into its own for designs with multiple clock domains.
Sadly the solution does not automate extraction of the timing data for setup and hold times from the devices used. It also has a known shortcoming for BlockRAM primitives which may yet prove unsolvable.